| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396 |
- (ns frontend.components.settings
- (:require [clojure.string :as string]
- [clojure.walk :as walk]
- [electron.ipc :as ipc]
- [frontend.colors :as colors]
- [frontend.common.missionary :as c.m]
- [frontend.components.assets :as assets]
- [frontend.components.file-sync :as fs]
- [frontend.components.shortcut :as shortcut]
- [frontend.components.svg :as svg]
- [frontend.config :as config]
- [frontend.context.i18n :refer [t]]
- [frontend.date :as date]
- [frontend.db :as db]
- [frontend.dicts :as dicts]
- [frontend.handler.config :as config-handler]
- [frontend.handler.db-based.rtc :as rtc-handler]
- [frontend.handler.db-based.vector-search-flows :as vector-search-flows]
- [frontend.handler.file-sync :as file-sync-handler]
- [frontend.handler.global-config :as global-config-handler]
- [frontend.handler.notification :as notification]
- [frontend.handler.plugin :as plugin-handler]
- [frontend.handler.property :as property-handler]
- [frontend.handler.route :as route-handler]
- [frontend.handler.ui :as ui-handler]
- [frontend.handler.user :as user-handler]
- [frontend.mobile.util :as mobile-util]
- [frontend.modules.instrumentation.core :as instrument]
- [frontend.modules.shortcut.data-helper :as shortcut-helper]
- [frontend.persist-db.browser :as db-browser]
- [frontend.spec.storage :as storage-spec]
- [frontend.state :as state]
- [frontend.storage :as storage]
- [frontend.ui :as ui]
- [frontend.util :refer [classnames web-platform?] :as util]
- [frontend.version :as fv]
- [goog.object :as gobj]
- [goog.string :as gstring]
- [logseq.db :as ldb]
- [logseq.shui.hooks :as hooks]
- [logseq.shui.ui :as shui]
- [missionary.core :as m]
- [promesa.core :as p]
- [reitit.frontend.easy :as rfe]
- [rum.core :as rum]))
- (defn toggle
- [label-for name state on-toggle & [detail-text]]
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for label-for}
- name]
- [:div.rounded-md.sm:max-w-tss.sm:col-span-2
- [:div.rounded-md {:style {:display "flex" :gap "1rem" :align-items "center"}}
- (ui/toggle state on-toggle true)
- detail-text]]])
- (rum/defcs app-updater < rum/reactive
- [state version]
- (let [update-pending? (state/sub :electron/updater-pending?)
- {:keys [type payload]} (state/sub :electron/updater)]
- [:span.cp__settings-app-updater
- [:div.ctls.flex.items-center
- [:div.mt-1.sm:mt-0.sm:col-span-2.flex.gap-4.items-center.flex-wrap
- [:div (cond
- (mobile-util/native-android?)
- (ui/button
- (t :settings-page/check-for-updates)
- :class "text-sm mr-1"
- :href "https://github.com/logseq/logseq/releases")
- (mobile-util/native-ios?)
- (ui/button
- (t :settings-page/check-for-updates)
- :class "text-sm mr-1"
- :href "https://apps.apple.com/app/logseq/id1601013908")
- (util/electron?)
- (ui/button
- (if update-pending? (t :settings-page/checking) (t :settings-page/check-for-updates))
- :class "text-sm mr-1"
- :disabled update-pending?
- :on-click #(js/window.apis.checkForUpdates false))
- :else
- nil)]
- [:div.text-sm.cursor
- {:title (str (t :settings-page/revision) config/revision)
- :on-click (fn []
- (notification/show! [:div "Current Revision: "
- [:a {:target "_blank"
- :href (str "https://github.com/logseq/logseq/commit/" config/revision)}
- config/revision]]
- :info
- false))}
- version]
- [:a.text-sm.fade-link.underline.inline
- {:target "_blank"
- :href "https://docs.logseq.com/#/page/changelog"}
- (t :settings-page/changelog)]]]
- (when-not (or update-pending?
- (string/blank? type))
- [:div.update-state.text-sm
- (case type
- "update-not-available"
- [:p (t :settings-page/app-updated)]
- "update-available"
- (let [{:keys [name url]} payload]
- [:p (str (t :settings-page/update-available))
- [:a.link
- {:on-click
- (fn [e]
- (js/window.apis.openExternal url)
- (util/stop e))}
- svg/external-link name " 🎉"]])
- "error"
- [:p (t :settings-page/update-error-1) [:br] (t :settings-page/update-error-2)
- [:a.link
- {:on-click
- (fn [e]
- (js/window.apis.openExternal "https://github.com/logseq/logseq/releases")
- (util/stop e))}
- svg/external-link " release channel"]])])]))
- (rum/defc outdenting-hint
- []
- [:div.ui__modal-panel
- {:style {:box-shadow "0 4px 20px 4px rgba(0, 20, 60, .1), 0 4px 80px -8px rgba(0, 20, 60, .2)"}}
- [:div {:style {:margin "12px" :max-width "500px"}}
- [:p.text-sm
- (t :settings-page/preferred-outdenting-tip)
- [:a.text-sm
- {:target "_blank" :href "https://discuss.logseq.com/t/whats-your-preferred-outdent-behavior-the-direct-one-or-the-logical-one/978"}
- (t :settings-page/preferred-outdenting-tip-more)]]
- [:img {:src "https://discuss.logseq.com/uploads/default/original/1X/e8ea82f63a5e01f6d21b5da827927f538f3277b9.gif"
- :width 500
- :height 500}]]])
- (rum/defc auto-expand-hint
- []
- [:div.ui__modal-panel
- {:style {:box-shadow "0 4px 20px 4px rgba(0, 20, 60, .1), 0 4px 80px -8px rgba(0, 20, 60, .2)"}}
- [:div {:style {:margin "12px" :max-width "500px"}}
- [:p.text-sm
- (t :settings-page/auto-expand-block-refs-tip)]
- [:img {:src "https://user-images.githubusercontent.com/28241963/225818326-118deda9-9d1e-477d-b0ce-771ca0bcd976.gif"
- :width 500
- :height 500}]]])
- (defn row-with-button-action
- [{:keys [left-label description action button-label href on-click desc -for stretch]}]
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4
- {:class "sm:items-start"}
- ;; left column
- [:div.flex.flex-col
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for -for}
- left-label]
- (when description
- [:div.text-xs.text-gray-10 description])]
- ;; right column
- [:div.mt-1.sm:mt-0.sm:col-span-2.flex.items-center
- {:style {:display "flex" :gap "0.5rem" :align-items "center"}}
- [:div {:style (when stretch {:width "100%"})}
- (if action action (shui/button
- {:as-child (not (string/blank? href))
- :size :sm
- :on-click on-click}
- (if (string/blank? href) button-label
- (shui/link {:href href} button-label))))]
- (when-not (or (util/mobile?)
- (mobile-util/native-platform?))
- [:div.text-sm.flex desc])]])
- (defn edit-config-edn []
- (row-with-button-action
- {:left-label (t :settings-page/custom-configuration)
- :button-label (t :settings-page/edit-config-edn)
- :href (rfe/href :file {:path (config/get-repo-config-path)})
- :on-click ui-handler/toggle-settings-modal!
- :-for "config_edn"}))
- (defn edit-global-config-edn []
- (row-with-button-action
- {:left-label (t :settings-page/custom-global-configuration)
- :button-label (t :settings-page/edit-global-config-edn)
- :href (rfe/href :file {:path (global-config-handler/global-config-path)})
- :on-click ui-handler/toggle-settings-modal!
- :-for "global_config_edn"}))
- (defn edit-custom-css []
- (row-with-button-action
- {:left-label (t :settings-page/custom-theme)
- :button-label (t :settings-page/edit-custom-css)
- :href (rfe/href :file {:path (config/get-custom-css-path)})
- :on-click ui-handler/toggle-settings-modal!
- :-for "customize_css"}))
- (defn edit-export-css []
- (row-with-button-action
- {:left-label (t :settings-page/export-theme)
- :button-label (t :settings-page/edit-export-css)
- :href (rfe/href :file {:path (config/get-export-css-path)})
- :on-click ui-handler/toggle-settings-modal!
- :-for "export_css"}))
- (defn show-brackets-row [t show-brackets?]
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for "show_brackets"}
- (t :settings-page/show-brackets)]
- [:div
- [:div.rounded-md.sm:max-w-xs
- (ui/toggle show-brackets?
- config-handler/toggle-ui-show-brackets!
- true)]]
- (when (not (or (util/mobile?) (mobile-util/native-platform?)))
- [:div {:style {:text-align "right"}}
- (ui/render-keyboard-shortcut (shortcut-helper/gen-shortcut-seq :ui/toggle-brackets))])])
- (defn toggle-wide-mode-row [t wide-mode?]
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for "wide_mode"}
- (t :settings-page/wide-mode)]
- [:div
- [:div.rounded-md.sm:max-w-xs
- (ui/toggle wide-mode?
- ui-handler/toggle-wide-mode!
- true)]]
- (when (not (or (util/mobile?) (mobile-util/native-platform?)))
- [:div {:style {:text-align "right"}}
- (ui/render-keyboard-shortcut (shortcut-helper/gen-shortcut-seq :ui/toggle-wide-mode))])])
- (defn editor-font-family-row [t font]
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for "font_family"}
- (t :settings-page/editor-font)]
- [:div.col-span-2.flex.gap-2
- (for [t [:default :serif :mono]
- :let [t (name t)
- tt (string/capitalize t)
- active? (= font t)]]
- (shui/button
- {:variant :secondary
- :class (when active? " border-primary border-[2px]")
- :style {:width "4.4rem"}
- :on-click #(state/set-editor-font! t)}
- [:span.flex.flex-col
- {:class (str "ls-font-" t)}
- [:strong "Ag"]
- [:small tt]]))]])
- (rum/defcs switch-spell-check-row < rum/reactive
- [state t]
- (let [enabled? (state/sub [:electron/user-cfgs :spell-check])]
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- (t :settings-page/spell-checker)]
- [:div
- [:div.rounded-md.sm:max-w-xs
- (ui/toggle
- enabled?
- (fn []
- (state/set-state! [:electron/user-cfgs :spell-check] (not enabled?))
- (p/then (ipc/ipc :userAppCfgs :spell-check (not enabled?))
- #(when (js/confirm (t :relaunch-confirm-to-work))
- (js/logseq.api.relaunch))))
- true)]]]))
- (rum/defcs switch-git-auto-commit-row < rum/reactive
- [state t]
- (let [enabled? (state/get-git-auto-commit-enabled?)]
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- (t :settings-page/git-switcher-label)]
- [:div
- [:div.rounded-md.sm:max-w-xs
- (ui/toggle
- enabled?
- (fn []
- (state/set-state! [:electron/user-cfgs :git/disable-auto-commit?] enabled?)
- (p/do!
- (ipc/ipc :userAppCfgs :git/disable-auto-commit? enabled?)
- (ipc/ipc :setGitAutoCommit)))
- true)]]]))
- (rum/defcs switch-git-commit-on-close-row < rum/reactive
- [state t]
- (let [enabled? (state/get-git-commit-on-close-enabled?)]
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- (t :settings-page/git-commit-on-close)]
- [:div
- [:div.rounded-md.sm:max-w-xs
- (ui/toggle
- enabled?
- (fn []
- (state/set-state! [:electron/user-cfgs :git/commit-on-close?] (not enabled?))
- (ipc/ipc :userAppCfgs :git/commit-on-close? (not enabled?)))
- true)]]]))
- (rum/defcs git-auto-commit-seconds < rum/reactive
- [state t]
- (let [secs (or (state/sub [:electron/user-cfgs :git/auto-commit-seconds]) 60)]
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- (t :settings-page/git-commit-delay)]
- [:div.mt-1.sm:mt-0.sm:col-span-2
- [:div.max-w-lg.rounded-md.sm:max-w-xs
- [:input#home-default-page.form-input.is-small.transition.duration-150.ease-in-out
- {:default-value secs
- :on-blur (fn [event]
- (let [value (-> (util/evalue event)
- util/safe-parse-int)]
- (if (and (number? value)
- (< 0 value (inc 86400)))
- (p/do!
- (state/set-state! [:electron/user-cfgs :git/auto-commit-seconds] value)
- (ipc/ipc :userAppCfgs :git/auto-commit-seconds value)
- (ipc/ipc :setGitAutoCommit))
- (when-let [elem (gobj/get event "target")]
- (notification/show!
- [:div "Invalid value! Must be a number between 1 and 86400"]
- :warning true)
- (gobj/set elem "value" secs)))))}]]]]))
- (rum/defc app-auto-update-row < rum/reactive [t]
- (let [enabled? (state/sub [:electron/user-cfgs :auto-update])
- enabled? (if (nil? enabled?) true enabled?)]
- (toggle "usage-diagnostics"
- (t :settings-page/auto-updater)
- enabled?
- #((state/set-state! [:electron/user-cfgs :auto-update] (not enabled?))
- (ipc/ipc :userAppCfgs :auto-update (not enabled?))))))
- (defn language-row [t preferred-language]
- (let [on-change (fn [e]
- (let [lang-code (util/evalue e)]
- (state/set-preferred-language! lang-code)
- (ui-handler/re-render-root!)))
- action [:select.form-select.is-small {:value preferred-language
- :on-change on-change}
- (for [language dicts/languages]
- (let [lang-code (name (:value language))
- lang-label (:label language)]
- [:option {:key lang-code :value lang-code} lang-label]))]]
- (row-with-button-action {:left-label (t :language)
- :-for "preferred_language"
- :action action})))
- (rum/defc theme-modes-row < rum/reactive
- [t]
- (let [theme (state/sub :ui/theme)
- dark? (= "dark" theme)
- system-theme? (state/sub :ui/system-theme?)
- switch-theme (if dark? "light" "dark")
- color-accent (state/sub :ui/radix-color)
- pick-theme [:ul.cp__theme-modes-options
- [:li {:on-click (partial state/use-theme-mode! "light")
- :class (classnames [{:active (and (not system-theme?) (not dark?))}])} [:i.mode-light {:class (when color-accent "radix")}] [:strong (t :settings-page/theme-light)]]
- [:li {:on-click (partial state/use-theme-mode! "dark")
- :class (classnames [{:active (and (not system-theme?) dark?)}])} [:i.mode-dark {:class (when color-accent "radix")}] [:strong (t :settings-page/theme-dark)]]
- [:li {:on-click (partial state/use-theme-mode! "system")
- :class (classnames [{:active system-theme?}])} [:i.mode-system {:class (when color-accent "radix")}] [:strong (t :settings-page/theme-system)]]]]
- (row-with-button-action {:left-label (t :right-side-bar/switch-theme (string/capitalize switch-theme))
- :-for "toggle_theme"
- :action pick-theme
- :desc (ui/render-keyboard-shortcut (shortcut-helper/gen-shortcut-seq :ui/toggle-theme))})))
- (rum/defc accent-color-row < rum/reactive
- [_in-modal?]
- (let [color-accent (state/sub :ui/radix-color)
- pick-theme [:div.cp__accent-colors-list-wrap
- {:class (if _in-modal? "as-modal-picker" "")}
- (for [color (concat [:none :logseq] colors/color-list)
- :let [active? (= color color-accent)
- none? (= color :none)]]
- [:div.flex.items-center
- (ui/tooltip
- (shui/button
- {: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"
- :auto-focus (and _in-modal? active?)
- :style {:background-color (colors/variable color :09)
- :outline-color (colors/variable color (if active? :07 :06))
- :outline-width (if active? "4px" "1px")
- :outline-style :solid
- :opacity (if active? 1 0.5)}
- :variant :text
- :on-click (fn [_e] (state/set-color-accent! color))}
- [:strong
- {:class (if none? "h-0.5 w-full bg-red-700"
- "w-2 h-2 rounded-full transition ease-in duration-100")
- :style {:background-color (if-not none? (str "var(--rx-" (name color) "-07)") "")
- :opacity (if (or none? active?) 1 0)}}])
- (case color
- :none [:p {:style {:max-width "300px"}}
- "Cancel accent color. This is currently in beta stage and mainly used for compatibility with custom themes."]
- :logseq "Logseq classical color"
- (str (name color) " color")))])]]
- [:div
- (row-with-button-action
- {:left-label (t :settings-page/accent-color)
- :-for "toggle_radix_theme"
- :desc (when-not _in-modal?
- [:span.pl-6 (ui/render-keyboard-shortcut
- (shortcut-helper/gen-shortcut-seq :ui/customize-appearance))])
- :stretch (boolean _in-modal?)
- :action pick-theme})
- [:div.text-sm.opacity-50.mt-1
- (t :settings-page/accent-color-alert)]]))
- (rum/defc appearance < rum/reactive
- []
- [:div#appearance_settings.cp__settings-appearance-modal-inner.w-96.p-4.shadow-xl
- (theme-modes-row t)
- (editor-font-family-row t (state/sub :ui/editor-font))
- (toggle-wide-mode-row t (state/sub :ui/wide-mode?))
- (show-brackets-row t (state/show-brackets?))
- (accent-color-row true)])
- (defn date-format-row [t preferred-date-format]
- [: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
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for "custom_date_format"}
- (t :settings-page/custom-date-format)
- (when-not (config/db-based-graph? (state/get-current-repo))
- (ui/tooltip [:span.flex.px-2 (svg/info)]
- [:span (t :settings-page/custom-date-format-warning)]))]
- [:div.mt-1.sm:mt-0.sm:col-span-2
- [:div.max-w-lg.rounded-md
- [:select.form-select.is-small
- {:value preferred-date-format
- :on-change (fn [e]
- (let [repo (state/get-current-repo)
- format (util/evalue e)
- db-based? (config/db-based-graph? repo)]
- (when-not (string/blank? format)
- (if db-based?
- (p/do!
- (property-handler/set-block-property! repo
- :logseq.class/Journal
- :logseq.property.journal/title-format
- format)
- (notification/show! "Please refresh the app for this change to take effect"))
- (do
- (config-handler/set-config! :journal/page-title-format format)
- (notification/show!
- [:div (t :settings-page/custom-date-format-notification)]
- :warning false)))
- (shui/dialog-close-all!)
- (when-not db-based? (route-handler/redirect! {:to :graphs})))))}
- (for [format (sort (date/journal-title-formatters))]
- [:option {:key format} format])]]]])
- (defn workflow-row [t preferred-workflow]
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for "preferred_workflow"}
- (t :settings-page/preferred-workflow)]
- [:div.mt-1.sm:mt-0.sm:col-span-2
- [:div.max-w-lg.rounded-md
- [:select.form-select.is-small
- {:value (name preferred-workflow)
- :on-change (fn [e]
- (-> (util/evalue e)
- string/lower-case
- keyword
- (#(if (= % :now) :now :todo))
- user-handler/set-preferred-workflow!))}
- (for [workflow [:now :todo]]
- [:option {:key (name workflow) :value (name workflow)}
- (if (= workflow :now) "NOW/LATER" "TODO/DOING")])]]]])
- (defn outdenting-row [t logical-outdenting?]
- (toggle "preferred_outdenting"
- [(t :settings-page/preferred-outdenting)
- (ui/tooltip [:span.flex.px-2 (svg/info)]
- (outdenting-hint) {:content-props {:side "right"}})]
- logical-outdenting?
- config-handler/toggle-logical-outdenting!))
- (defn showing-full-blocks [t show-full-blocks?]
- (toggle "show_full_blocks"
- (t :settings-page/show-full-blocks)
- show-full-blocks?
- config-handler/toggle-show-full-blocks!))
- (defn preferred-pasting-file [t preferred-pasting-file?]
- (toggle "preferred_pasting_file"
- [(t :settings-page/preferred-pasting-file)
- (ui/tooltip [:span.flex.px-2 (svg/info)]
- [:span.block.w-64 (t :settings-page/preferred-pasting-file-hint)])]
- preferred-pasting-file?
- config-handler/toggle-preferred-pasting-file!))
- (defn auto-expand-row [t auto-expand-block-refs?]
- (toggle "auto_expand_block_refs"
- [(t :settings-page/auto-expand-block-refs)
- (ui/tooltip [:span.flex.px-2 (svg/info)]
- (auto-expand-hint))]
- auto-expand-block-refs?
- config-handler/toggle-auto-expand-block-refs!))
- (defn tooltip-row [t enable-tooltip?]
- (toggle "enable_tooltip"
- (t :settings-page/enable-tooltip)
- enable-tooltip?
- (fn []
- (config-handler/toggle-ui-enable-tooltip!))))
- (defn shortcut-tooltip-row [t enable-shortcut-tooltip?]
- (toggle "enable_tooltip"
- (t :settings-page/enable-shortcut-tooltip)
- enable-shortcut-tooltip?
- (fn []
- (state/toggle-shortcut-tooltip!))))
- (defn timetracking-row [t enable-timetracking?]
- (toggle "enable_timetracking"
- (t :settings-page/enable-timetracking)
- enable-timetracking?
- #(let [value (not enable-timetracking?)]
- (config-handler/set-config! :feature/enable-timetracking? value))))
- (defn update-home-page
- [event]
- (let [value (util/evalue event)]
- (cond
- (string/blank? value)
- (let [home (get (state/get-config) :default-home {})
- new-home (dissoc home :page)]
- (p/do!
- (config-handler/set-config! :default-home new-home)
- (config-handler/set-config! :feature/enable-journals? true)
- (notification/show! "Journals enabled" :success)))
- ;; FIXME: home page should be db id instead of page name
- (ldb/get-page (db/get-db) value)
- (let [home (get (state/get-config) :default-home {})
- new-home (assoc home :page value)]
- (config-handler/set-config! :default-home new-home)
- (notification/show! "Home default page updated successfully!" :success))
- :else
- (notification/show! (str "The page \"" value "\" doesn't exist yet. Please create that page first, and then try again.") :warning))))
- (defn journal-row [enable-journals?]
- (toggle "enable_journals"
- (t :settings-page/enable-journals)
- enable-journals?
- (fn []
- (let [value (not enable-journals?)]
- (config-handler/set-config! :feature/enable-journals? value)))))
- (defn enable-all-pages-public-row [t enable-all-pages-public?]
- (toggle "all pages public"
- (t :settings-page/enable-all-pages-public)
- enable-all-pages-public?
- (fn []
- (let [value (not enable-all-pages-public?)]
- (config-handler/set-config! :publishing/all-pages-public? value)))))
- (defn zotero-settings-row []
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for "zotero_settings"}
- "Zotero"]
- [:div.mt-1.sm:mt-0.sm:col-span-2
- [:div
- (ui/button
- (t :settings)
- :class "text-sm"
- :style {:margin-top "0px"}
- :on-click
- (fn []
- (state/close-settings!)
- (route-handler/redirect! {:to :zotero-setting})))]]])
- (defn auto-push-row [_t current-repo enable-git-auto-push?]
- (when (and current-repo (string/starts-with? current-repo "https://"))
- (toggle "enable_git_auto_push"
- "Enable Git auto push"
- enable-git-auto-push?
- (fn []
- (let [value (not enable-git-auto-push?)]
- (config-handler/set-config! :git-auto-push value))))))
- (defn usage-diagnostics-row [t instrument-disabled?]
- (toggle "usage-diagnostics"
- (t :settings-page/disable-sentry)
- (not instrument-disabled?)
- (fn [] (instrument/disable-instrument
- (not instrument-disabled?)))
- [:span.text-sm.opacity-50 (t :settings-page/disable-sentry-desc)]))
- ;; (defn clear-cache-row [t]
- ;; (row-with-button-action {:left-label (t :settings-page/clear-cache)
- ;; :button-label (t :settings-page/clear)
- ;; :on-click #(state/pub-event! [:graph/clear-cache!])
- ;; :-for "clear_cache"}))
- (defn version-row [t version]
- (row-with-button-action {:left-label (t :settings-page/current-version)
- :action (app-updater version)
- :-for "current-version"}))
- (defn developer-mode-row [t developer-mode?]
- (toggle "developer_mode"
- (t :settings-page/developer-mode)
- developer-mode?
- (fn []
- (let [mode (not developer-mode?)]
- (state/set-developer-mode! mode)))
- [:div.text-sm.opacity-50 (t :settings-page/developer-mode-desc)]))
- (rum/defc plugin-enabled-switcher
- [t]
- (let [value (state/lsp-enabled?-or-theme)
- [on? set-on?] (rum/use-state value)
- on-toggle #(let [v (not on?)]
- (set-on? v)
- (storage/set ::storage-spec/lsp-core-enabled v))]
- [:div.flex.items-center.gap-2
- (ui/toggle on? on-toggle true)
- (when (util/electron?)
- (when (not= (boolean value) on?)
- (ui/button (t :plugin/restart)
- :on-click #(js/logseq.api.relaunch)
- :small? true :intent "logseq")))]))
- (rum/defc http-server-enabled-switcher
- [t]
- (let [[value _] (rum/use-state (boolean (storage/get ::storage-spec/http-server-enabled)))
- [on? set-on?] (rum/use-state value)
- on-toggle #(let [v (not on?)]
- (set-on? v)
- (storage/set ::storage-spec/http-server-enabled v))]
- [:div.flex.items-center.gap-2
- (ui/toggle on? on-toggle true)
- (when (not= (boolean value) on?)
- (ui/button (t :plugin/restart)
- :on-click #(js/logseq.api.relaunch)
- :small? true :intent "logseq"))]))
- (rum/defc flashcards-enabled-switcher
- [enable-flashcards?]
- (ui/toggle enable-flashcards?
- (fn []
- (let [value (not enable-flashcards?)]
- (config-handler/set-config! :feature/enable-flashcards? value)))
- true))
- (rum/defc user-proxy-settings
- [{:keys [type protocol host port] :as agent-opts}]
- (ui/button [:span.flex.items-center
- [:span.pr-1
- (case type
- "system" "System Default"
- "direct" "Direct"
- (and protocol host port (str protocol "://" host ":" port)))]
- (ui/icon "edit")]
- :class "text-sm"
- :on-click #(state/pub-event! [:go/proxy-settings agent-opts])))
- (defn plugin-system-switcher-row []
- (row-with-button-action
- {:left-label (t :settings-page/plugin-system)
- :action (plugin-enabled-switcher t)}))
- (defn http-server-switcher-row []
- (row-with-button-action
- {:left-label "HTTP APIs server"
- :action (http-server-enabled-switcher t)}))
- (defn flashcards-switcher-row [enable-flashcards?]
- (row-with-button-action
- {:left-label (t :settings-page/enable-flashcards)
- :action (flashcards-enabled-switcher enable-flashcards?)}))
- (defn https-user-agent-row [agent-opts]
- (row-with-button-action
- {:left-label (t :settings-page/network-proxy)
- :action (user-proxy-settings agent-opts)}))
- (rum/defcs auto-chmod-row < rum/reactive
- [state t]
- (let [enabled? (if (= nil (state/sub [:electron/user-cfgs :feature/enable-automatic-chmod?]))
- true
- (state/sub [:electron/user-cfgs :feature/enable-automatic-chmod?]))]
- (toggle
- "automatic-chmod"
- (t :settings-page/auto-chmod)
- enabled?
- #(do
- (state/set-state! [:electron/user-cfgs :feature/enable-automatic-chmod?] (not enabled?))
- (ipc/ipc :userAppCfgs :feature/enable-automatic-chmod? (not enabled?)))
- [:span.text-sm.opacity-50 (t :settings-page/auto-chmod-desc)])))
- (rum/defcs native-titlebar-row < rum/reactive
- [state t]
- (let [enabled? (state/sub [:electron/user-cfgs :window/native-titlebar?])]
- (toggle
- "native-titlebar"
- (t :settings-page/native-titlebar)
- enabled?
- #(when (js/confirm (t :relaunch-confirm-to-work))
- (state/set-state! [:electron/user-cfgs :window/native-titlebar?] (not enabled?))
- (ipc/ipc :userAppCfgs :window/native-titlebar? (not enabled?))
- (js/logseq.api.relaunch))
- [:span.text-sm.opacity-50 (t :settings-page/native-titlebar-desc)])))
- (rum/defcs settings-general < rum/reactive
- [_state current-repo]
- (let [preferred-language (state/sub [:preferred-language])
- show-radix-themes? true
- editor-font (state/sub :ui/editor-font)]
- [:div.panel-wrap.is-general
- (version-row t fv/version)
- (language-row t preferred-language)
- (theme-modes-row t)
- (editor-font-family-row t editor-font)
- (when (and (util/electron?) (not util/mac?)) (native-titlebar-row t))
- (when show-radix-themes? (accent-color-row false))
- (when (config/global-config-enabled?) (edit-global-config-edn))
- (when current-repo (edit-config-edn))
- (when current-repo (edit-custom-css))
- (when current-repo (edit-export-css))]))
- (defn file-format-row [t preferred-format]
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for "preferred_format"}
- (t :settings-page/preferred-file-format)]
- [:div.mt-1.sm:mt-0.sm:col-span-2
- [:div.max-w-lg.rounded-md
- [:select.form-select.is-small
- {:value (name preferred-format)
- :on-change (fn [e]
- (let [format (-> (util/evalue e)
- (string/lower-case)
- keyword)]
- (user-handler/set-preferred-format! format)))}
- (for [format (map name [:org :markdown])]
- [:option {:key format :value format} (string/capitalize format)])]]]])
- (rum/defcs settings-editor < rum/reactive
- [_state current-repo]
- (let [preferred-format (state/get-preferred-format)
- preferred-date-format (state/get-date-formatter)
- preferred-workflow (state/get-preferred-workflow)
- enable-timetracking? (state/enable-timetracking?)
- enable-all-pages-public? (state/all-pages-public?)
- logical-outdenting? (state/logical-outdenting?)
- show-full-blocks? (state/show-full-blocks?)
- preferred-pasting-file? (state/preferred-pasting-file?)
- auto-expand-block-refs? (state/auto-expand-block-refs?)
- enable-tooltip? (state/enable-tooltip?)
- enable-shortcut-tooltip? (state/sub :ui/shortcut-tooltip?)
- show-brackets? (state/show-brackets?)
- wide-mode? (state/sub :ui/wide-mode?)
- enable-git-auto-push? (state/enable-git-auto-push? current-repo)
- db-graph? (config/db-based-graph? (state/get-current-repo))]
- [:div.panel-wrap.is-editor
- (when-not db-graph?
- (file-format-row t preferred-format))
- (date-format-row t preferred-date-format)
- (when-not db-graph?
- (workflow-row t preferred-workflow))
- (show-brackets-row t show-brackets?)
- (toggle-wide-mode-row t wide-mode?)
- (when (util/electron?) (switch-spell-check-row t))
- (outdenting-row t logical-outdenting?)
- (showing-full-blocks t show-full-blocks?)
- (preferred-pasting-file t preferred-pasting-file?)
- (auto-expand-row t auto-expand-block-refs?)
- (when-not (or (util/mobile?) (mobile-util/native-platform?))
- (shortcut-tooltip-row t enable-shortcut-tooltip?))
- (when-not (or (util/mobile?) (mobile-util/native-platform?))
- (tooltip-row t enable-tooltip?))
- (timetracking-row t enable-timetracking?)
- (enable-all-pages-public-row t enable-all-pages-public?)
- (auto-push-row t current-repo enable-git-auto-push?)]))
- (rum/defc settings-git
- []
- [:div.panel-wrap
- [:div.text-sm.my-4
- (ui/admonition
- :tip
- [:p (t :settings-page/git-tip)])
- [:span.text-sm.opacity-50.my-4
- (t :settings-page/git-desc-1)]
- [:br] [:br]
- [:span.text-sm.opacity-50.my-4
- (t :settings-page/git-desc-2)]
- [:a {:href "https://git-scm.com/" :target "_blank"}
- "Git"]
- [:span.text-sm.opacity-50.my-4
- (t :settings-page/git-desc-3)]]
- [:br]
- (switch-git-auto-commit-row t)
- (switch-git-commit-on-close-row t)
- (git-auto-commit-seconds t)])
- (rum/defc settings-advanced < rum/reactive
- []
- (let [instrument-disabled? (state/sub :instrument/disabled?)
- developer-mode? (state/sub [:ui/developer-mode?])
- https-agent-opts (state/sub [:electron/user-cfgs :settings/agent])]
- [:div.panel-wrap.is-advanced
- (when (and (or util/mac? util/win32?) (util/electron?)) (app-auto-update-row t))
- (usage-diagnostics-row t instrument-disabled?)
- (when-not (mobile-util/native-platform?) (developer-mode-row t developer-mode?))
- (when (util/electron?) (https-user-agent-row https-agent-opts))
- (when (util/electron?) (auto-chmod-row t))
- ;; (clear-cache-row t)
- ;; (ui/admonition
- ;; :warning
- ;; [:p (t :settings-page/clear-cache-warning)])
- ]))
- (rum/defc sync-enabled-switcher
- [enabled?]
- (ui/toggle enabled?
- (fn []
- (file-sync-handler/set-sync-enabled! (not enabled?)))
- true))
- (rum/defc sync-diff-merge-enabled-switcher
- [enabled?]
- (ui/toggle enabled?
- (fn []
- (file-sync-handler/set-sync-diff-merge-enabled! (not enabled?)))
- true))
- (defn sync-switcher-row [repo enabled?]
- (row-with-button-action
- (cond-> {:left-label (t :settings-page/sync)
- :action (sync-enabled-switcher enabled?)}
- (config/db-based-graph? repo)
- (merge {:action nil :desc "Not available yet for database graphs"}))))
- (defn sync-diff-merge-switcher-row [enabled?]
- (row-with-button-action
- {:left-label (str (t :settings-page/sync-diff-merge) " (Experimental!)") ;; Not included in i18n to avoid outdating translations
- :action (sync-diff-merge-enabled-switcher enabled?)
- :desc (ui/tooltip [:span.inline-flex.px-1 (svg/info)]
- [:div
- [:div (t :settings-page/sync-diff-merge-desc)]
- [:div (t :settings-page/sync-diff-merge-warn)]])}))
- (rum/defc whiteboards-enabled-switcher
- [enabled?]
- (ui/toggle enabled?
- (fn []
- (let [value (not enabled?)]
- (config-handler/set-config! :feature/enable-whiteboards? value)))
- true))
- (defn whiteboards-switcher-row [enabled?]
- (row-with-button-action
- {:left-label (t :settings-page/enable-whiteboards)
- :action (whiteboards-enabled-switcher enabled?)}))
- (rum/defc settings-account-usage-description [pro-account? graph-usage]
- (let [count-usage (count graph-usage)
- count-limit (if pro-account? 10 1)
- count-percent (js/Math.round (/ count-usage count-limit 0.01))
- storage-usage (->> (map :used-gbs graph-usage)
- (reduce + 0))
- storage-usage-formatted (cond
- (zero? storage-usage) "0.0"
- (< storage-usage 0.01) "Less than 0.01"
- :else (gstring/format "%.2f" storage-usage))
- ;; TODO: check logic on this. What are the rules around storage limits?
- ;; do we, and should we be able to, give individual users more storage?
- ;; should that be on a per graph or per user basis?
- default-storage-limit (if pro-account? 10 0.05)
- storage-limit (->> (range 0 count-limit)
- (map #(get-in graph-usage [% :limit-gbs] default-storage-limit))
- (reduce + 0))
- storage-percent (/ storage-usage storage-limit 0.01)
- storage-percent-formatted (gstring/format "%.1f" storage-percent)]
- [:div.text-sm
- (when pro-account?
- [:<>
- (gstring/format "%s of %s synced graphs " count-usage count-limit)
- [:strong.text-white (gstring/format "(%s%%)" count-percent)]
- ", "])
- (gstring/format "%sGB of %sGB total storage " storage-usage-formatted storage-limit)
- [:strong.text-white (gstring/format "(%s%%)" storage-percent-formatted)]]))
- ; storage-usage-formatted "GB of " storage-limit "GB total storage"
- ; [:strong.text-white " (" storage-percent-formatted "%)"]]))
- (rum/defc settings-account-usage-graphs [_pro-account? graph-usage]
- (when (< 0 (count graph-usage))
- [:div.grid.gap-3 {:style {:grid-template-columns (str "repeat(" (count graph-usage) ", 1fr)")}}
- (for [{:keys [name used-percent]} graph-usage
- :let [color (if (<= 100 used-percent) "bg-red-500" "bg-blue-500")]]
- [:div.rounded-full.w-full.h-2 {:class "bg-black/50"
- :tooltip name}
- [:div.rounded-full.h-2 {:class color
- :style {:width (str used-percent "%")
- :min-width "0.5rem"
- :max-width "100%"}}]])]))
- (rum/defc ^:large-vars/cleanup-todo settings-account < rum/reactive
- []
- (let [current-graph-uuid (state/sub-current-file-sync-graph-uuid)
- graph-usage (state/get-remote-graph-usage)
- current-graph-is-remote? ((set (map :uuid graph-usage)) current-graph-uuid)
- logged-in? (user-handler/logged-in?)
- user-info (state/get-user-info)
- paid-user? (#{"active" "on_trial" "cancelled"} (:LemonStatus user-info))
- gift-user? (some #{"pro"} (:UserGroups user-info))
- pro-account? (or paid-user? gift-user?)
- expiration-date (some-> user-info :LemonEndsAt date/parse-iso)
- renewal-date (some-> user-info :LemonRenewsAt date/parse-iso)
- has-subscribed? (some? (:LemonStatus user-info))]
- [:div.panel-wrap.is-features.mb-8
- [:div.mt-1.sm:mt-0.sm:col-span-2
- (cond
- logged-in?
- [:div.grid.grid-cols-3.gap-8.pt-2
- [:div "Current plan"]
- [:div.col-span-2
- [:div {:class "w-full bg-gray-500/10 rounded-lg p-4 flex flex-col gap-4"}
- [:div.flex.gap-4.items-center
- (if pro-account?
- [:div.flex-1 "Pro"]
- [:div.flex-1 "Free"])
- (cond
- has-subscribed?
- (ui/button "Manage plan" {:class "p-1 h-8 justify-center"
- :disabled true
- :icon "upload"})
- ; :on-click user-handler/upgrade})
- (not pro-account?)
- (ui/button "Upgrade plan" {:class "p-1 h-8 justify-center"
- :icon "upload"
- :on-click user-handler/upgrade})
- :else nil)]
- (settings-account-usage-graphs pro-account? graph-usage)
- (settings-account-usage-description pro-account? graph-usage)
- (if current-graph-is-remote?
- (ui/button "Deactivate syncing" {:class "p-1 h-8 justify-center"
- :disabled true
- :background "gray"
- :icon "cloud-off"})
- (ui/button "Activate syncing" {:class "p-1 h-8 justify-center"
- :background "blue"
- :icon "cloud"
- :on-click #(fs/maybe-onboarding-show :sync-initiate)}))]]
- (when has-subscribed?
- [:<>
- [:div "Billing"]
- [:div.col-span-2.flex.flex-col.gap-4
- (cond
- ;; If there is no expiration date, print the renewal date
- (and renewal-date (nil? expiration-date))
- [:div
- [:strong.font-semibold "Next billing date: "
- (date/get-locale-string renewal-date)]]
- ;; If the expiration date is in the future, word it as such
- (< (js/Date.) expiration-date)
- [:div
- [:strong.font-semibold "Pro plan expires on: "
- (date/get-locale-string expiration-date)]]
- ;; Otherwise, ind
- :else
- [:div
- [:strong.font-semibold "Pro plan expired on: "
- (date/get-locale-string expiration-date)]])
- [:div (ui/button "Open invoices" {:class "w-full h-8 p-1 justify-center"
- :disabled true
- :background "gray"
- :icon "receipt"})]]])
- [:div "Profile"]
- [:div.col-span-2.grid.grid-cols-2.gap-4
- [:div.flex.flex-col.gap-2.box-border {:class "basis-1/2"}
- [:label.text-sm.font-semibold "First name"]
- [:input.rounded.border.px-2.py-1.box-border {:class "border-blue-500 bg-black/25 w-full"}]]
- [:div.flex.flex-col.gap-2 {:class "basis-1/2"}
- [:label.text-sm.font-semibold "Last name"]
- [:input.rounded.border.px-2.py-1.box-border {:class "border-blue-500 bg-black/25 w-full"}]]
- [:div.flex-1.flex.flex-col.gap-2.col-span-2
- [:label.text-sm.font-semibold "Username"]
- [:input.rounded.border.px-2.py-1.box-border {:class "border-blue-500 bg-black/25"
- :value (user-handler/email)}]]]
- [:div "Authentication"]
- [:div.col-span-2
- [:div.grid.grid-cols-2.gap-4
- [:div (ui/button (t :logout) {:class "p-1 h-8 justify-center w-full"
- :background "gray"
- :icon "logout"
- :on-click user-handler/logout})]
- [:div (ui/button "Reset password" {:class "p-1 h-8 justify-center w-full"
- :disabled true
- :background "gray"
- :icon "key"
- :on-click user-handler/logout})]
- [:div.col-span-2 (ui/button "Delete Account" {:class "p-1 h-8 justify-center w-full"
- :disabled true
- :background "red"})]]]]
- (not logged-in?)
- [:div.grid.grid-cols-3.gap-8.pt-2
- [:div "Authentication"]
- [:div.col-span-2.flex.flex-wrap.gap-4
- [:div.w-full.text-white "With a Logseq account, you can access cloud-based services like Logseq Sync and alpha/beta features."]
- [:div.flex-1 (ui/button "Sign up" {:class "h-8 w-full text-center justify-center"
- :on-click (fn []
- (state/close-settings!)
- (state/pub-event! [:user/login]))})]
- [:div.flex-1 (ui/button (t :login) {:icon "login"
- :class "h-8 w-full text-center justify-center"
- :background "gray"
- :on-click (fn []
- (state/close-settings!)
- (state/pub-event! [:user/login]))})]]
- [:div.col-span-3.flex.flex-col.gap-4 {:class "bg-black/20 p-4 rounded-lg"}
- [:div.flex.w-full.items-center
- [:div {:class "w-1/2 text-lg"}
- "Discover the power of "
- [:strong {:class "text-white/80"} "Logseq Sync"]]
- [: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"}
- [:div.w-3.h-3.rounded-full.bg-green-500]
- "Synced"]]
- [:div.flex.w-full.gap-4
- [:div {:class "w-1/2 bg-black/50 rounded-lg p-4 pt-10 relative flex flex-col gap-4"}
- [:div.absolute.top-0.left-4.bg-gray-700.uppercase.px-2.py-1.rounded-b-lg.font-bold.text-xs "Free"]
- [:div
- [:strong.text-white.text-xl.font-normal "$0"]]
- [:div.text-white.font-bold {:class "h-[2.5rem] "} "Get started with basic syncing"]
- [:ul.text-xs.list-none.m-0.flex.flex-col.gap-0.5
- [:li "Unlimited unsynced graphs"]
- [:li "1 synced graph (up to 50MB, notes only)"]
- [:li "No asset syncing"]
- [:li "Access to core Logseq features"]]]
- [:div {:class "w-1/2 bg-black/50 rounded-lg p-4 pt-10 relative flex flex-col gap-4"}
- [:div.absolute.top-0.left-4.bg-blue-700.uppercase.px-2.py-1.rounded-b-lg.font-bold.text-xs "Pro"]
- [:div
- [:strong.text-white.text-xl.font-normal "$10"]
- [:span.text-xs.font-base {:class "ml-0.5"} "/ month"]]
- [:div.text-white.font-bold {:class "h-[2.5rem]"} "Unlock advanced syncing and more"]
- [:ul.text-xs.list-none.m-0.flex.flex-col.gap-0.5
- [:li "Unlimited unsynced graphs"]
- [:li "10 synced graphs (up to 5GB each)"]
- [:li "Sync assets up to 100MB per file"]
- [:li "Early access to alpha/beta features"]
- [:li "Upcoming cloud-based features, including Logseq Publish"]]]]]])]]))
- (rum/defc settings-features < rum/reactive
- []
- (let [current-repo (state/get-current-repo)
- enable-journals? (state/enable-journals? current-repo)
- enable-flashcards? (state/enable-flashcards? current-repo)
- enable-sync? (state/enable-sync?)
- enable-sync-diff-merge? (state/enable-sync-diff-merge?)
- db-based? (config/db-based-graph? current-repo)
- enable-whiteboards? (state/enable-whiteboards? current-repo)
- logged-in? (user-handler/logged-in?)]
- [:div.panel-wrap.is-features.mb-8
- (journal-row enable-journals?)
- (when (not enable-journals?)
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
- [:label.block.text-sm.font-medium.leading-5.opacity-70
- {:for "default page"}
- (t :settings-page/home-default-page)]
- [:div.mt-1.sm:mt-0.sm:col-span-2
- [:div.max-w-lg.rounded-md.sm:max-w-xs
- [:input#home-default-page.form-input.is-small.transition.duration-150.ease-in-out
- {:default-value (state/sub-default-home-page)
- :on-blur update-home-page
- :on-key-press (fn [e]
- (when (= "Enter" (util/ekey e))
- (update-home-page e)))}]]]])
- (when-not db-based? (whiteboards-switcher-row enable-whiteboards?))
- (when (and web-platform? config/feature-plugin-system-on?)
- (plugin-system-switcher-row))
- (when (util/electron?)
- (http-server-switcher-row))
- (flashcards-switcher-row enable-flashcards?)
- (when-not db-based? (zotero-settings-row))
- (when-not web-platform?
- [:div.mt-1.sm:mt-0.sm:col-span-2
- [:hr]
- (if logged-in?
- [:div
- (user-handler/email)
- [:p (ui/button (t :logout) {:class "p-1"
- :icon "logout"
- :on-click user-handler/logout})]]
- [:div
- (ui/button (t :login) {:class "p-1"
- :icon "login"
- :on-click (fn []
- (state/close-settings!)
- (state/pub-event! [:user/login]))})
- [:p.text-sm.opacity-50 (t :settings-page/login-prompt)]])])
- (when-not web-platform?
- [:<>
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
- [:label.flex.font-medium.leading-5.self-start.mt-1
- (ui/icon (if logged-in? "lock-open" "lock") {:class "mr-1"}) (t :settings-page/beta-features)]]
- [:div.flex.flex-col.gap-4
- {:class (when-not user-handler/alpha-or-beta-user? "opacity-50 pointer-events-none cursor-not-allowed")}
- (sync-switcher-row current-repo enable-sync?)
- (when (and enable-sync? (not (config/db-based-graph? current-repo)))
- (sync-diff-merge-switcher-row enable-sync-diff-merge?))
- [:div.text-sm
- (t :settings-page/sync-desc-1)
- [:a.mx-1 {:href "https://blog.logseq.com/how-to-setup-and-use-logseq-sync/"
- :target "_blank"}
- (t :settings-page/sync-desc-2)]
- (t :settings-page/sync-desc-3)]]])]))
- ;; (when-not web-platform?
- ;; [:<>
- ;; [:hr]
- ;; [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
- ;; [: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)]]
- ;; [:div.flex.flex-col.gap-4
- ;; {:class (when-not user-handler/alpha-user? "opacity-50 pointer-events-none cursor-not-allowed")}
- ;; ;; features
- ;; ]])
- (def DEFAULT-ACTIVE-TAB-STATE (if config/ENABLE-SETTINGS-ACCOUNT-TAB [:account :account] [:general :general]))
- (rum/defc settings-effect
- < rum/static
- [active]
- (hooks/use-effect!
- (fn []
- (let [active (and (sequential? active) (name (first active)))
- ^js ds (.-dataset js/document.body)]
- (if active
- (set! (.-settingsTab ds) active)
- (js-delete ds "settingsTab"))
- #(js-delete ds "settingsTab")))
- [active])
- [:<>])
- (rum/defcs settings-collaboration < rum/reactive
- (rum/local "" ::invite-email)
- {:will-mount (fn [state]
- (rtc-handler/<rtc-get-users-info)
- state)}
- [state]
- (let [*invite-email (::invite-email state)
- current-repo (state/get-current-repo)
- users (get (state/sub :rtc/users-info) current-repo)]
- [:div.panel-wrap.is-collaboration.mb-8
- [:div.flex.flex-col.gap-2.mt-4
- [:h2.opacity-50.font-medium "Members:"]
- [:div.users.flex.flex-col.gap-1
- (for [{user-name :user/name
- user-email :user/email
- graph<->user-user-type :graph<->user/user-type} users]
- [:div.flex.flex-row.items-center.gap-2 {:key (str "user-" user-name)}
- [:div user-name]
- (when user-email [:div.opacity-50.text-sm user-email])
- (when graph<->user-user-type [:div.opacity-50.text-sm (name graph<->user-user-type)])])]
- [:div.flex.flex-col.gap-4.mt-4
- (shui/input
- {:placeholder "Email address"
- :on-change #(reset! *invite-email (util/evalue %))})
- (shui/button
- {:on-click (fn []
- (let [user-email @*invite-email
- graph-uuid (ldb/get-graph-rtc-uuid (db/get-db))]
- (when-not (string/blank? user-email)
- (when graph-uuid
- (rtc-handler/<rtc-invite-email graph-uuid user-email)))))}
- "Invite")]]]))
- (rum/defc settings-ai
- []
- (let [[model-info set-model-info] (hooks/use-state nil)
- [load-model-progress set-load-model-progress] (hooks/use-state nil)
- {:keys [status]} load-model-progress
- repo (state/get-current-repo)
- current-model (:graph-text-embedding-model-name model-info)
- [webgpu? set-webgpu?] (hooks/use-state nil)]
- (hooks/use-effect!
- (fn []
- (p/let [webgpu? (db-browser/<check-webgpu-available?)]
- (set-webgpu? webgpu?)))
- [])
- (hooks/use-effect!
- (fn []
- (c.m/run-task
- ::fetch-model-info
- (m/reduce
- (constantly nil)
- (m/ap
- (m/?> vector-search-flows/infer-worker-ready-flow)
- (let [model-info (c.m/<? (state/<invoke-db-worker :thread-api/vec-search-embedding-model-info repo))]
- (set-model-info model-info))))
- :succ (constantly nil)))
- [])
- (hooks/use-effect!
- (fn []
- (c.m/run-task
- ::update-load-model-progress
- (m/reduce
- (fn [_ v] (set-load-model-progress (walk/keywordize-keys v)))
- vector-search-flows/load-model-progress-flow)
- :succ (constantly nil)))
- [])
- [:div.panel-wrap
- [:div.flex.flex-col.gap-2.mt-4
- [:div.font-medium.text-muted-foreground.text-sm "Semantic search:"]
- [:div.flex.flex-col.gap-2
- [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
- [:label.block.text-sm.font-medium.leading-8.opacity-70
- {:for "local-embedding-model"}
- "Local embedding model"]
- [:div.rounded-md.sm:max-w-tss.sm:col-span-2
- (if webgpu?
- [:div.flex.flex-col.gap-2
- (shui/select
- (cond->
- {:on-value-change (fn [model-name]
- (c.m/run-task
- ::load-model
- (m/sp
- (set-model-info (assoc model-info :graph-text-embedding-model-name model-name))
- (c.m/<?
- (state/<invoke-db-worker :thread-api/vec-search-load-model repo model-name))
- (c.m/<?
- (state/<invoke-db-worker :thread-api/vec-search-cancel-indexing repo))
- (c.m/<?
- (state/<invoke-db-worker :thread-api/vec-search-re-embedding-graph-data repo)))
- :succ (constantly nil)))}
- current-model
- (assoc :value current-model))
- (shui/select-trigger
- {:class "h-8"}
- (shui/select-value
- {:placeholder "Select a model"}))
- (shui/select-content
- (shui/select-group
- (for [model-name (:available-model-names model-info)]
- (shui/select-item {:value model-name} model-name)))))
- (when status
- [:div.text-muted-foreground.text-sm
- (let [{:keys [file progress loaded total]} load-model-progress]
- (case status
- ("progress" "download" "initiate")
- (str "Downloading " file
- (when progress
- (util/format " %d/%dm"
- (int (/ loaded 1024 1024))
- (int (/ total 1024 1024)))))
- "done"
- (str "Downloaded " file)
- "ready"
- "Model is ready 🚀"
- nil))])]
- [:div.warning "WebGPU is not supported on this browser, please upgrade it or using another browser."])]]]]]))
- (rum/defcs ^:large-vars/cleanup-todo settings
- < (rum/local DEFAULT-ACTIVE-TAB-STATE ::active)
- {:will-mount
- (fn [state]
- (state/load-app-user-cfgs)
- state)
- :did-mount
- (fn [state]
- (let [active-tab (first (:rum/args state))
- *active (::active state)]
- (when (keyword? active-tab)
- (reset! *active [active-tab nil])))
- state)
- :will-unmount
- (fn [state]
- (state/close-settings!)
- state)}
- rum/reactive
- [state _active-tab]
- (let [current-repo (state/sub :git/current-repo)
- _installed-plugins (state/sub :plugin/installed-plugins)
- plugins-of-settings (and config/lsp-enabled? (seq (plugin-handler/get-enabled-plugins-if-setting-schema)))
- *active (::active state)
- logged-in? (user-handler/logged-in?)
- db-based? (config/db-based-graph?)]
- [:div#settings.cp__settings-main
- (settings-effect @*active)
- [:div.cp__settings-inner
- [:aside.md:w-64 {:style {:min-width "10rem"}}
- [:header.cp__settings-header
- [:h1.cp__settings-modal-title (t :settings)]]
- [:ul.settings-menu
- (for [[label id text icon]
- [(when config/ENABLE-SETTINGS-ACCOUNT-TAB
- [:account "account" (t :settings-page/tab-account) (ui/icon "user-circle")])
- [:general "general" (t :settings-page/tab-general) (ui/icon "adjustments")]
- [:editor "editor" (t :settings-page/tab-editor) (ui/icon "writing")]
- [:keymap "keymap" (t :settings-page/tab-keymap) (ui/icon "keyboard")]
- (when db-based?
- [:ai (t :settings-page/tab-ai) (t :settings-page/ai) (ui/icon "wand")])
- (when (util/electron?)
- [:version-control "git" (t :settings-page/tab-version-control) (ui/icon "history")])
- ;; (when (util/electron?)
- ;; [:assets "assets" (t :settings-page/tab-assets) (ui/icon "box")])
- [:advanced "advanced" (t :settings-page/tab-advanced) (ui/icon "bulb")]
- [:features "features" (t :settings-page/tab-features) (ui/icon "app-feature")]
- (when logged-in?
- [:collaboration "collaboration" (t :settings-page/tab-collaboration) (ui/icon "users")])
- (when plugins-of-settings
- [:plugins-setting "plugins" (t :settings-of-plugins) (ui/icon "puzzle")])]]
- (when label
- [:li.settings-menu-item
- {:key text
- :data-id id
- :class (util/classnames [{:active (= label (first @*active))}])
- :on-click (fn []
- (if (= label :plugins-setting)
- (state/pub-event! [:go/plugins-settings (:id (first plugins-of-settings))])
- (reset! *active [label (first @*active)])))}
- [:a.flex.items-center.settings-menu-link icon [:strong text]]]))]]
- [:article
- [:header.cp__settings-header
- [:h1.cp__settings-category-title (t (keyword (str "settings-page/tab-" (name (first @*active)))))]]
- (case (first @*active)
- :plugins-setting
- (let [label (second @*active)]
- (state/pub-event! [:go/plugins-settings (:id (first plugins-of-settings))])
- (reset! *active [label label])
- nil)
- :account
- (settings-account)
- :general
- (settings-general current-repo)
- :editor
- (settings-editor current-repo)
- :keymap
- (shortcut/shortcut-keymap-x)
- :version-control
- (settings-git)
- :assets
- (assets/settings-content)
- :advanced
- (settings-advanced)
- :features
- (settings-features)
- :collaboration
- (settings-collaboration)
- :ai
- (settings-ai)
- nil)]]]))
|