(ns frontend.handler "Main ns that handles application startup. Closest ns that we have to a system. Contains a couple of small system components" (:require [cljs.reader :refer [read-string]] [clojure.string :as string] [electron.ipc :as ipc] [electron.listener :as el] [frontend.components.block :as block] [frontend.components.editor :as editor] [frontend.components.page :as page] [frontend.components.reference :as reference] [frontend.components.whiteboard :as whiteboard] [frontend.config :as config] [frontend.context.i18n :as i18n :refer [t]] [frontend.db :as db] [frontend.db.restore :as db-restore] [frontend.db.conn :as conn] [frontend.db.persist :as db-persist] [frontend.db.react :as react] [frontend.error :as error] [frontend.extensions.srs :as srs] [frontend.handler.command-palette :as command-palette] [frontend.handler.events :as events] [frontend.handler.file :as file-handler] [frontend.handler.global-config :as global-config-handler] [frontend.handler.notification :as notification] [frontend.handler.page :as page-handler] [frontend.handler.plugin-config :as plugin-config-handler] [frontend.handler.repo :as repo-handler] [frontend.handler.repo-config :as repo-config-handler] [frontend.handler.ui :as ui-handler] [frontend.handler.user :as user-handler] [frontend.idb :as idb] [frontend.mobile.util :as mobile-util] [frontend.modules.instrumentation.core :as instrument] [frontend.modules.outliner.datascript :as outliner-db] [frontend.modules.outliner.file :as file] [frontend.modules.shortcut.core :as shortcut] [frontend.state :as state] [frontend.ui :as ui] [frontend.util :as util] [frontend.util.persist-var :as persist-var] [goog.object :as gobj] [lambdaisland.glogi :as log] [promesa.core :as p] [frontend.mobile.core :as mobile] [frontend.db.listener :as db-listener] [frontend.db.rtc.core :as rtc-core] [cljs-bean.core :as bean])) (defn- set-global-error-notification! [] (set! js/window.onerror (fn [message, _source, _lineno, _colno, error] (when-not (error/ignored? message) (log/error :exception error) ;; (notification/show! ;; (str "message=" message "\nsource=" source "\nlineno=" lineno "\ncolno=" colno "\nerror=" error) ;; :error ;; ;; Don't auto-hide ;; false) )))) (defn- watch-for-date! [] (let [f (fn [] #_:clj-kondo/ignore (let [repo (state/get-current-repo)] (when (or (config/db-based-graph? repo) (and (not (state/nfs-refreshing?)) (not (contains? (:file/unlinked-dirs @state/state) (config/get-repo-dir repo))))) ;; Don't create the journal file until user writes something (page-handler/create-today-journal!))))] (f) (js/setInterval f 5000))) (defn- instrument! [] (let [total (srs/get-srs-cards-total)] (state/set-state! :srs/cards-due-count total))) (defn restore-and-setup! [repos] (when-let [repo (or (state/get-current-repo) (:url (first repos)))] (-> (db-restore/restore-graph! repo) (p/then (fn [] (db-listener/listen-and-persist! repo) ;; try to load custom css only for current repo (ui-handler/add-style-if-exists!) (-> (p/do! (repo-config-handler/start {:repo repo}) (when (config/global-config-enabled?) (global-config-handler/start {:repo repo})) (when (config/plugin-config-enabled?) (plugin-config-handler/start))) (p/finally (fn [] ;; install after config is restored (shortcut/refresh!) (cond (and (not (seq (db/get-files config/local-repo))) ;; Not native local directory (not (some config/local-file-based-graph? (map :url repos))) (not (mobile-util/native-platform?))) ;; will execute `(state/set-db-restoring! false)` inside (repo-handler/setup-local-repo-if-not-exists!) :else (state/set-db-restoring! false))))))) (p/then (fn [] (js/console.log "db restored, setting up repo hooks") (state/pub-event! [:modal/nfs-ask-permission]) (page-handler/init-commands!) (watch-for-date!) (file-handler/watch-for-current-graph-dir!) (state/pub-event! [:graph/restored (state/get-current-repo)]))) (p/catch (fn [error] (log/error :exception error)))))) (defn- handle-connection-change [e] (let [online? (= (gobj/get e "type") "online")] (state/set-online! online?))) (defn set-network-watcher! [] (js/window.addEventListener "online" handle-connection-change) (js/window.addEventListener "offline" handle-connection-change)) (defn enable-datalog-console "Enables datalog console in browser provided by https://github.com/homebaseio/datalog-console" [] (js/document.documentElement.setAttribute "__datalog-console-remote-installed__" true) (.addEventListener js/window "message" (fn [event] (let [db (conn/get-db)] (when-let [devtool-message (gobj/getValueByKeys event "data" ":datalog-console.client/devtool-message")] (let [msg-type (:type (read-string devtool-message))] (case msg-type :datalog-console.client/request-whole-database-as-string (.postMessage js/window #js {":datalog-console.remote/remote-message" (pr-str db)} "*") nil))))))) (defn clear-cache! [] (notification/show! "Clearing..." :warning false) (p/let [_ (when (util/electron?) (ipc/ipc "clearCache")) _ (idb/clear-local-storage-and-idb!)] (js/setTimeout (fn [] (if (util/electron?) (ipc/ipc :reloadWindowPage) (js/window.location.reload))) 2000))) ;; FIXME: Another get-repos implementation at src\main\frontend\handler\repo.cljs (defn- get-repos [] (p/let [nfs-dbs (db-persist/get-all-graphs)] ;; TODO: Better IndexDB migration handling (cond (and (mobile-util/native-platform?) (some #(or (string/includes? % " ") (string/includes? % "logseq_local_/")) nfs-dbs)) (do (notification/show! ["DB version is not compatible, please clear cache then re-add your graph back." (ui/button (t :settings-page/clear-cache) :class "ui__modal-enter" :class "text-sm p-1" :on-click clear-cache!)] :error false) {:url config/local-repo :example? true}) (seq nfs-dbs) (map (fn [db] {:url db :nfs? true}) nfs-dbs) :else [{:url config/local-repo :example? true}]))) (defn- register-components-fns! [] (state/set-page-blocks-cp! page/page) (state/set-component! :block/linked-references reference/block-linked-references) (state/set-component! :whiteboard/tldraw-preview whiteboard/tldraw-preview) (state/set-component! :block/single-block block/single-block-cp) (state/set-component! :editor/box editor/box) (command-palette/register-global-shortcut-commands)) (reset! db-listener/*db-listener outliner-db/after-transact-pipelines) (defn- get-system-info [] (when (util/electron?) (p/let [info (ipc/ipc :system/info)] (state/set-state! :system/info (bean/->clj info))))) (defn start! [render] (get-system-info) (set-global-error-notification!) (set! js/window.onhashchange #(state/hide-custom-context-menu!)) ;; close context menu when page navs (register-components-fns!) (user-handler/restore-tokens-from-localstorage) (state/set-db-restoring! true) (when (util/electron?) (el/listen!)) (render) (i18n/start) (instrument/init) (state/set-online! js/navigator.onLine) (set-network-watcher!) (-> (util/indexeddb-check?) (p/catch (fn [_e] (notification/show! "Sorry, it seems that your browser doesn't support IndexedDB, we recommend to use latest Chrome(Chromium) or Firefox(Non-private mode)." :error false) (state/set-indexedb-support! false)))) (idb/start) (react/run-custom-queries-when-idle!) (events/run!) (p/do! (when (mobile-util/native-platform?) (mobile/mobile-preinit)) (-> (p/let [repos (get-repos) _ (state/set-repos! repos) _ (mobile-util/hide-splash) ;; hide splash as early as ui is stable _ (restore-and-setup! repos)] (when (mobile-util/native-platform?) (state/restore-mobile-theme!))) (p/catch (fn [e] (js/console.error "Error while restoring repos: " e))) (p/finally (fn [] (state/set-db-restoring! false)))) (file/