Quellcode durchsuchen

feat(electron): add compatible AppTitleBar for Mac & Win32

charlie vor 4 Jahren
Ursprung
Commit
3bfd51caa3

+ 15 - 1
resources/js/preload.js

@@ -1,6 +1,6 @@
 const fs = require('fs')
 const path = require('path')
-const { ipcRenderer, contextBridge, shell, clipboard } = require('electron')
+const { ipcRenderer, contextBridge, shell, clipboard, BrowserWindow } = require('electron')
 
 const IS_MAC = process.platform === 'darwin'
 const IS_WIN32 = process.platform === 'win32'
@@ -84,5 +84,19 @@ contextBridge.exposeInMainWorld('apis', {
 
       return clipboard.read('public.file-url').replace('file://', '')
     }
+  },
+
+  toggleMaxOrMinActiveWindow (isToggleMin = false) {
+    ipcRenderer.invoke('toggle-max-or-min-active-win', isToggleMin)
+  },
+
+  /**
+   * internal
+   * @param type
+   * @param args
+   * @private
+   */
+  async _callApplication (type, ...args) {
+    return await ipcRenderer.invoke('call-application', type, ...args)
   }
 })

+ 49 - 29
src/electron/electron/core.cljs

@@ -5,15 +5,13 @@
             [clojure.string :as string]
             ["fs" :as fs]
             ["path" :as path]
-            ["electron" :refer [BrowserWindow app protocol] :as electron]))
+            ["electron" :refer [BrowserWindow app protocol ipcMain] :as electron]))
 
 (def ROOT_PATH (path/join js/__dirname ".."))
-(def MAIN_WINDOW_ENTRY (str "file://" (path/join js/__dirname (if dev? "dev.html" "index.html"))))
+(def MAIN_WINDOW_ENTRY (str "file://" (path/join js/__dirname (if dev? "electron.html" "index.html"))))
 
-(def ^:dynamic *setup-fn* nil)
-(def ^:dynamic *teardown-fn* nil)
-(def ^:dynamic *teardown-updater* nil)
-(def ^:dynamic *teardown-interceptor* nil)
+(defonce *setup-fn (volatile! nil))
+(defonce *teardown-fn (volatile! nil))
 
 ;; Handle creating/removing shortcuts on Windows when installing/uninstalling.
 (when (js/require "electron-squirrel-startup") (.quit app))
@@ -21,8 +19,10 @@
 (defn create-main-window
   "create main app window"
   []
-  (let [win-opts {:width  980
-                  :height 700
+  (let [win-opts {:width         980
+                  :height        700
+                  :frame         false
+                  :titleBarStyle (if mac? "hidden" nil)
                   :webPreferences
                   {:nodeIntegration         false
                    :nodeIntegrationInWorker false
@@ -35,13 +35,10 @@
     win))
 
 (defn setup-updater! [^js win]
-  (.. log (info (str "Logseq App(" (.getVersion app) ") Starting... ")))
-
   ;; manual updater
-  (set! *teardown-updater*
-        (init-updater {:repo   "logseq/logseq"
-                       :logger log
-                       :win    win})))
+  (init-updater {:repo   "logseq/logseq"
+                 :logger log
+                 :win    win}))
 
 (defn setup-interceptor! []
   (.registerFileProtocol
@@ -50,7 +47,31 @@
      (let [url (.-url request)
            path (string/replace url "assets://" "")]
        (callback #js {:path path}))))
-  (set! *teardown-interceptor* #(.unregisterProtocol protocol "assets")))
+  #(.unregisterProtocol protocol "assets"))
+
+(defn setup-app-manager!
+  [^js win]
+  (let [toggle-win-channel "toggle-max-or-min-active-win"
+        call-app-channel "call-application"]
+    (doto ipcMain
+      (.handle toggle-win-channel
+               (fn [_ toggle-min?]
+                 (when-let [active-win (.getFocusedWindow BrowserWindow)]
+                   (if toggle-min?
+                     (if (.isMinimized active-win)
+                       (.restore active-win)
+                       (.minimize active-win))
+                     (if (.isMaximized active-win)
+                       (.unmaximize active-win)
+                       (.maximize active-win))))))
+      (.handle call-app-channel
+               (fn [_ type & args]
+                 (try
+                   (js-invoke app type args)
+                   (catch js/Error e
+                          (js/console.error e))))))
+    #(do (.removeHandler ipcMain toggle-win-channel)
+         (.removeHandler ipcMain call-app-channel))))
 
 (defn main
   []
@@ -61,22 +82,21 @@
                *win (atom win)
                *quitting? (atom false)]
 
-           (set! *setup-fn*
-                 (fn []
-                   ;; updater
-                   (setup-updater! win)
-                   (setup-interceptor!)
+           (.. log (info (str "Logseq App(" (.getVersion app) ") Starting... ")))
 
-                   ;; handler
-                   (handler/set-ipc-handler! win)
+           (vreset! *setup-fn
+                    (fn []
+                      (let [t0 (setup-updater! win)
+                            t1 (setup-interceptor!)
+                            t2 (setup-app-manager! win)
+                            tt (handler/set-ipc-handler! win)]
 
-                   ;; teardown
-                   #(do
-                      (when *teardown-updater* (*teardown-updater*))
-                      (when *teardown-interceptor* (*teardown-interceptor*)))))
+                        (vreset! *teardown-fn
+                                 #(doseq [f [t0 t1 t2 tt]]
+                                    (and f (f)))))))
 
            ;; setup effects
-           (*setup-fn*)
+           (@*setup-fn)
 
            ;; main window events
            (.on win "close" #(if (or @*quitting? win32?)
@@ -88,8 +108,8 @@
 
 (defn start []
   (js/console.log "Main - start")
-  (when *setup-fn* (*setup-fn*)))
+  (when @*setup-fn (@*setup-fn)))
 
 (defn stop []
   (js/console.log "Main - stop")
-  (when *teardown-fn* (*teardown-fn*)))
+  (when @*teardown-fn (@*teardown-fn)))

+ 10 - 8
src/electron/electron/handler.cljs

@@ -117,11 +117,13 @@
   (println "Error: no ipc handler for: " (bean/->js args)))
 
 (defn set-ipc-handler! [window]
-  (.handle ipcMain "main"
-           (fn [event args-js]
-             (try
-               (let [message (bean/->clj args-js)]
-                 (bean/->js (handle window message)))
-               (catch js/Error e
-                 (println "IPC error: " e)
-                 e)))))
+  (let [main-channel "main"]
+    (.handle ipcMain main-channel
+             (fn [event args-js]
+               (try
+                 (let [message (bean/->clj args-js)]
+                   (bean/->js (handle window message)))
+                 (catch js/Error e
+                   (println "IPC error: " e)
+                   e))))
+    #(.removeHandler ipcMain main-channel)))

+ 1 - 1
src/electron/electron/updater.cljs

@@ -117,6 +117,6 @@
     (.handle ipcMain check-channel check-listener)
     (.handle ipcMain install-channel install-listener)
     #(do
-       (.removeHandler ipcMain install-listener)
+       (.removeHandler ipcMain install-channel)
        (.removeHandler ipcMain check-channel)
        (reset! *update-pending nil))))

+ 1 - 1
src/main/frontend/components/header.cljs

@@ -22,7 +22,7 @@
 (rum/defc logo < rum/reactive
   [{:keys [white?]}]
   [:a.cp__header-logo
-   {:href "/"
+   {:href     (rfe/href :home)
     :on-click (fn []
                 (util/scroll-to-top)
                 (state/set-journals-length! 1))}

+ 16 - 0
src/main/frontend/components/svg.cljs

@@ -83,6 +83,22 @@
    [:path.opacity-75 {:fill "currentColor"
                       :d "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"}]])
 
+(defonce minus
+  [:svg.w-6.h-6
+   {:viewbox "0 0 24 24", :stroke "currentColor", :fill "none"}
+   [:path
+    {:d               "M20 12H4"
+     :stroke-width    "2"
+     :stroke-linejoin "round"
+     :stroke-linecap  "round"}]])
+
+(defonce rectangle
+  [:svg.w-6.h-6
+   {:viewbox "0 0 24 24", :stroke "currentColor", :fill "none"}
+   [:path
+    {:d               "M3.16580358,18.5038125 L20.5529464,18.5038125 C22.6525178,18.5038125 23.7072321,17.4593839 23.7072321,15.3902411 L23.7072321,3.12495537 C23.7072321,1.0558125 22.6525178,0.0113839219 20.5529464,0.0113839219 L3.16580358,0.0113839219 C1.07651787,0.0118125 0.0115178672,1.04638392 0.0115178672,3.12495537 L0.0115178672,15.3906696 C0.0115178672,17.4696696 1.07651787,18.5042411 3.16580358,18.5042411 L3.16580358,18.5038125 Z M3.19580358,16.8868125 C2.19123216,16.8868125 1.62894642,16.3545268 1.62894642,15.3096696 L1.62894642,3.20638392 C1.62894642,2.16152679 2.19123213,1.62924108 3.19580358,1.62924108 L20.5229464,1.62924108 C21.5172321,1.62924108 22.0898036,2.16152679 22.0898036,3.20638392 L22.0898036,15.3092411 C22.0898036,16.3540982 21.5172322,16.8863839 20.5229464,16.8863839 L3.19580358,16.8868125 Z"
+     :stroke-width    "2"}]])
+
 (defn- hero-icon
   ([d]
    (hero-icon d {}))

+ 27 - 10
src/main/frontend/components/theme.cljs

@@ -1,17 +1,34 @@
 (ns frontend.components.theme
-  (:require [rum.core :as rum]))
+  (:require [rum.core :as rum]
+            [frontend.util :as util]
+            [frontend.version :refer [version]]
+            [frontend.components.svg :as svg]))
+
+(rum/defc frame-title-bar
+  []
+  [:div.ls-window-frame-title-bar
+   [:div.l
+    [:a.it {:title "Go Back" :on-click #(js/window.history.back)} (svg/big-arrow-left)]
+    [:a.it {:title "Go Forward" :on-click #(js/window.history.forward)} (svg/big-arrow-right)]]
+   ; TODO: center region should display current page title or important background notifications
+   [:span.c (str "Logseq - " version)]
+   [:div.r
+    [:a.it {:title "Minimize Window" :on-click #(js/window.apis.toggleMaxOrMinActiveWindow true)} svg/minus]
+    [:a.it.maximize {:title "Maximize Window" :on-click #(js/window.apis.toggleMaxOrMinActiveWindow)} svg/rectangle]
+    [:a.it {:title "Close Window" :on-click #(js/window.apis._callApplication "quit")} svg/close]]])
 
 (rum/defc container
   [{:keys [theme on-click] :as props} child]
-  rum/use-effect! (let [doc js/document.documentElement
-                        cls (.-classList doc)]
-                    (.setAttribute doc "data-theme" (if (= theme "white") "light" theme))
-                    (if (= theme "dark")                    ;; for tailwind dark mode
-                      (.add cls "dark")
-                      (.remove cls "dark")))
-
-  [theme]
+  (rum/use-effect!
+   #(let [doc js/document.documentElement
+          cls (.-classList doc)]
+      (.setAttribute doc "data-theme" (if (= theme "white") "light" theme))
+      (if (= theme "dark")                                 ;; for tailwind dark mode
+        (.add cls "dark")
+        (.remove cls "dark")))
+   [theme])
   [:div
-   {:class (str theme "-theme")
+   {:class    (str theme "-theme")
     :on-click on-click}
+   (when (util/electron?) (frame-title-bar))
    child])

+ 74 - 0
src/main/frontend/components/theme.css

@@ -91,3 +91,77 @@ html[data-theme=light] {
     display: none;
   }
 }
+
+html.is-electron {
+  --frame-top-height: 24px;
+
+  .theme-inner {
+    padding-top: var(--frame-top-height);
+  }
+
+  .cp__header {
+    height: 2rem;
+    top: var(--frame-top-height);
+  }
+
+  .cp__right-sidebar {
+    top: 4rem;
+  }
+
+  &.is-mac {
+    .ls-window-frame-title-bar {
+      padding-left: 70px;
+    }
+  }
+
+  .ls-window-frame-title-bar {
+    background-color: var(--ls-primary-background-color);
+    position: fixed;
+    left: 0;
+    right: 0;
+    z-index: 9;
+    height: var(--frame-top-height);
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    user-select: none;
+    -webkit-app-region: drag;
+
+    & > .l, & > .r {
+      display: flex;
+    }
+
+    & > .c {
+      font-size: .7rem;
+    }
+
+    a.it {
+      padding: 0 2px;
+      cursor: pointer;
+      -webkit-app-region: no-drag;
+
+      &:hover {
+        background-color: var(--ls-secondary-background-color);
+      }
+
+      &:active {
+        background-color: var(--ls-primary-background-color);
+      }
+
+      svg {
+        transform: scale(.6);
+        color: var(--ls-primary-text-color);
+        cursor: pointer;
+      }
+
+      &.maximize {
+        svg {
+          transform: scale(.5) translateY(2px) translateX(1px);
+          opacity: .7;
+        }
+      }
+    }
+  }
+}
+
+

+ 2 - 0
src/main/frontend/ui.cljs

@@ -231,6 +231,8 @@
   []
   (let [cl (.-classList js/document.documentElement)]
     (if util/mac? (.add cl "is-mac"))
+    (if util/win32? (.add cl "is-win32"))
+    (if (util/electron?) (.add cl "is-electron"))
     (if (util/ios?) (.add cl "is-ios"))
     (if (util/mobile?) (.add cl "is-mobile"))
     (if (util/safari?) (.add cl "is-safari"))))

+ 3 - 0
src/main/frontend/util.cljc

@@ -954,6 +954,9 @@
 (defonce mac? #?(:cljs goog.userAgent/MAC
                  :clj nil))
 
+(defonce win32? #?(:cljs goog.userAgent/WINDOWS
+                 :clj nil))
+
 (defn ->system-modifier
   [keyboard-shortcut]
   (if mac?