diff --git a/apps/web/desktop/config/windowstate.js b/apps/web/desktop/config/window-state.ts
similarity index 69%
rename from apps/web/desktop/config/windowstate.js
rename to apps/web/desktop/config/window-state.ts
index fd4ff3444..a122c4e1d 100644
--- a/apps/web/desktop/config/windowstate.js
+++ b/apps/web/desktop/config/window-state.ts
@@ -18,36 +18,63 @@ along with this program. If not, see .
*/
import { JSONStorage } from "../jsonstorage";
-import { screen as _screen, remote } from "electron";
-const screen = _screen || remote.screen;
+import { screen as _screen, BrowserWindow } from "electron";
+const screen = _screen;
-class WindowState {
- constructor(options) {
- this.winRef = null;
+type WindowStateOptions = {
+ storageKey: string;
+ maximize: boolean;
+ fullScreen: boolean;
+ defaultWidth: number;
+ defaultHeight: number;
+};
+
+type SerializableWindowState = {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ displayBounds?: Electron.Rectangle;
+ isMaximized?: boolean;
+ isFullScreen?: boolean;
+};
+
+export class WindowState {
+ private readonly config: WindowStateOptions;
+ private state: SerializableWindowState;
+ private windowRef: BrowserWindow | undefined;
+ private readonly eventHandlingDelay = 100;
+ private stateChangeTimer: NodeJS.Timeout | undefined;
+
+ constructor(options?: Partial) {
+ this.windowRef = undefined;
this.stateChangeTimer = undefined;
- this.eventHandlingDelay = 100;
this.config = {
storageKey: "windowState",
maximize: true,
fullScreen: true,
+ defaultHeight: 800,
+ defaultWidth: 600,
...options
};
// Load previous state
- this.state = JSONStorage.get(this.config.storageKey, {});
+ const defaultState: SerializableWindowState = {
+ width: this.config.defaultWidth,
+ height: this.config.defaultHeight,
+ x: 0,
+ y: 0
+ };
+ this.state = JSONStorage.get(
+ this.config.storageKey,
+ defaultState
+ );
// Check state validity
- this.validateState();
-
- // Set state fallback values
- this.state = {
- width: this.config.defaultWidth || 800,
- height: this.config.defaultHeight || 600,
- ...this.state
- };
+ this.validateState(defaultState);
}
- isNormal(win) {
+ isNormal(win: BrowserWindow) {
return !win.isMaximized() && !win.isMinimized() && !win.isFullScreen();
}
@@ -76,7 +103,9 @@ class WindowState {
};
}
- windowWithinBounds(bounds) {
+ windowWithinBounds(bounds: Electron.Rectangle) {
+ if (!this.state) return false;
+
return (
this.state.x >= bounds.x &&
this.state.y >= bounds.y &&
@@ -97,12 +126,12 @@ class WindowState {
}
}
- validateState() {
+ validateState(defaultState: SerializableWindowState) {
const isValid =
this.state &&
(this.hasBounds() || this.state.isMaximized || this.state.isFullScreen);
if (!isValid) {
- this.state = null;
+ this.state = defaultState;
return;
}
@@ -111,8 +140,8 @@ class WindowState {
}
}
- updateState(win) {
- win = win || this.winRef;
+ updateState(win?: BrowserWindow) {
+ win = win || this.windowRef;
if (!win) {
return;
}
@@ -133,7 +162,7 @@ class WindowState {
}
}
- saveState(win) {
+ saveState(win?: BrowserWindow) {
// Update window state only if it was provided
if (win) {
this.updateState(win);
@@ -145,7 +174,7 @@ class WindowState {
stateChangeHandler = () => {
// Handles both 'resize' and 'move'
- clearTimeout(this.stateChangeTimer);
+ if (this.stateChangeTimer) clearTimeout(this.stateChangeTimer);
this.stateChangeTimer = setTimeout(
() => this.updateState(),
this.eventHandlingDelay
@@ -163,7 +192,7 @@ class WindowState {
this.saveState();
};
- manage(win) {
+ manage(win: BrowserWindow) {
if (this.config.maximize && this.state.isMaximized) {
win.maximize();
}
@@ -174,17 +203,17 @@ class WindowState {
win.on("move", this.stateChangeHandler);
win.on("close", this.closeHandler);
win.on("closed", this.closedHandler);
- this.winRef = win;
+ this.windowRef = win;
}
unmanage() {
- if (this.winRef) {
- this.winRef.removeListener("resize", this.stateChangeHandler);
- this.winRef.removeListener("move", this.stateChangeHandler);
- clearTimeout(this.stateChangeTimer);
- this.winRef.removeListener("close", this.closeHandler);
- this.winRef.removeListener("closed", this.closedHandler);
- this.winRef = null;
+ if (this.windowRef) {
+ this.windowRef.removeListener("resize", this.stateChangeHandler);
+ this.windowRef.removeListener("move", this.stateChangeHandler);
+ if (this.stateChangeTimer) clearTimeout(this.stateChangeTimer);
+ this.windowRef.removeListener("close", this.closeHandler);
+ this.windowRef.removeListener("closed", this.closedHandler);
+ this.windowRef = undefined;
}
}
@@ -216,5 +245,3 @@ class WindowState {
return this.state.isFullScreen;
}
}
-
-export { WindowState };
diff --git a/apps/web/desktop/electron.js b/apps/web/desktop/electron.js
index 99a1239b6..64693461f 100644
--- a/apps/web/desktop/electron.js
+++ b/apps/web/desktop/electron.js
@@ -27,7 +27,7 @@ import { getBackgroundColor, getTheme, setTheme } from "./config/theme";
import getZoomFactor from "./ipc/calls/getZoomFactor";
import { logger } from "./logger";
import { setupMenu } from "./menu";
-import { WindowState } from "./config/windowstate";
+import { WindowState } from "./config/window-state";
import { sendMessageToRenderer } from "./ipc/utils";
import { EVENTS } from "./events";
import "./ipc/index.js";
@@ -38,6 +38,7 @@ import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { getDesktopIntegration } from "./config/desktopIntegration";
import { AutoLaunch } from "./autolaunch";
+import bringToFront from "./ipc/actions/bringToFront";
if (!RELEASE) {
require("electron-reloader")(module);
@@ -251,14 +252,14 @@ function setupTray() {
label: "Show Notesnook",
type: "normal",
icon: APP_ICON_PATH,
- click: showApp
+ click: bringToFront
},
{ type: "separator" },
{
label: "New note",
type: "normal",
click: () => {
- showApp();
+ bringToFront();
sendMessageToRenderer(EVENTS.createItem, { itemType: "note" });
}
},
@@ -266,7 +267,7 @@ function setupTray() {
label: "New notebook",
type: "normal",
click: () => {
- showApp();
+ bringToFront();
sendMessageToRenderer(EVENTS.createItem, { itemType: "notebook" });
}
},
@@ -279,24 +280,12 @@ function setupTray() {
}
}
]);
- tray.on("double-click", showApp);
- tray.on("click", showApp);
+ tray.on("double-click", bringToFront);
+ tray.on("click", bringToFront);
tray.setToolTip("Notesnook");
tray.setContextMenu(contextMenu);
}
-function showApp() {
- if (globalThis.window.isMinimized()) {
- if (mainWindowState.isMaximized) {
- globalThis.window.maximize();
- } else globalThis.window.restore();
- }
- globalThis.window.show();
- globalThis.window.focus();
- globalThis.window.moveTop();
- globalThis.window.webContents.focus();
-}
-
function setupDesktopIntegration() {
const desktopIntegration = getDesktopIntegration();
diff --git a/apps/web/desktop/ipc/actions/bringToFront.js b/apps/web/desktop/ipc/actions/bringToFront.js
index f67a95ff4..86e4c0128 100644
--- a/apps/web/desktop/ipc/actions/bringToFront.js
+++ b/apps/web/desktop/ipc/actions/bringToFront.js
@@ -17,8 +17,18 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
+import { WindowState } from "../../config/window-state";
+
export default () => {
if (!globalThis.window) return;
+
+ if (globalThis.window.isMinimized()) {
+ if (new WindowState({}).isMaximized) {
+ globalThis.window.maximize();
+ } else globalThis.window.restore();
+ }
globalThis.window.show();
+ globalThis.window.focus();
globalThis.window.moveTop();
+ globalThis.window.webContents.focus();
};