TelegramPWA/src/electron/windowState.ts
2023-09-30 01:55:55 +02:00

204 lines
4.8 KiB
TypeScript

import type { BrowserWindow, Rectangle } from 'electron';
import { screen } from 'electron';
import { store } from './utils';
type Options = {
defaultHeight?: number;
defaultWidth?: number;
fullScreen?: boolean;
maximize?: boolean;
};
type State = {
displayBounds: {
height: number;
width: number;
};
width: number;
height: number;
x: number;
y: number;
isFullScreen: boolean;
isMaximized: boolean;
urlHash: string;
};
export type WindowState = State & {
manage: (window: Electron.BrowserWindow) => void;
unmanage: () => void;
resetStateToDefault: () => void;
saveLastUrlHash: () => void;
clearLastUrlHash: () => void;
};
const EVENT_HANDLING_DELAY = 100;
const STORE_KEY = 'window-state';
const DEFAULT_OPTIONS = {
defaultHeight: 600,
defaultWidth: 800,
maximize: true,
fullScreen: true,
};
function windowStateKeeper(options: Options): WindowState {
let state: State;
let winRef: BrowserWindow | undefined;
let stateChangeTimer: ReturnType<typeof setTimeout>;
options = {
...DEFAULT_OPTIONS,
...options,
};
function isNormal(win: BrowserWindow): boolean {
return !win.isMaximized() && !win.isMinimized() && !win.isFullScreen();
}
function hasBounds(): boolean {
return state
&& Number.isInteger(state.x)
&& Number.isInteger(state.y)
&& Number.isInteger(state.width) && state.width > 0
&& Number.isInteger(state.height) && state.height > 0;
}
function resetStateToDefault() {
const displayBounds = screen.getPrimaryDisplay().bounds;
state = {
width: options.defaultWidth!,
height: options.defaultHeight!,
x: 0,
y: 0,
displayBounds,
isMaximized: false,
isFullScreen: false,
urlHash: '',
};
}
function windowWithinBounds(bounds: Rectangle) {
return state.x >= bounds.x
&& state.y >= bounds.y
&& state.x + state.width <= bounds.x + bounds.width
&& state.y + state.height <= bounds.y + bounds.height;
}
function ensureWindowVisibleOnSomeDisplay() {
const visible = screen.getAllDisplays().some((display) => windowWithinBounds(display.bounds));
if (!visible) {
resetStateToDefault();
}
}
function validateState() {
const isValid = state && (hasBounds() || state.isMaximized || state.isFullScreen);
if (!isValid) {
resetStateToDefault();
return;
}
if (hasBounds() && state.displayBounds) {
ensureWindowVisibleOnSomeDisplay();
}
}
function updateState() {
if (!winRef) {
return;
}
// Don't throw an error when window was closed
try {
const winBounds = winRef.getBounds();
if (isNormal(winRef)) {
state.x = winBounds.x;
state.y = winBounds.y;
state.width = winBounds.width;
state.height = winBounds.height;
}
state.isMaximized = winRef.isMaximized();
state.isFullScreen = winRef.isFullScreen();
state.displayBounds = screen.getDisplayMatching(winBounds).bounds;
} catch (err) {
// Handler not supported, ignoring
}
}
function handleStateChange() {
clearTimeout(stateChangeTimer);
stateChangeTimer = setTimeout(updateState, EVENT_HANDLING_DELAY);
}
function handleClose() {
updateState();
}
function handleClosed() {
unmanage();
store.set(STORE_KEY, state);
}
function manage(win: BrowserWindow) {
if (options.maximize && state.isMaximized) {
win.maximize();
}
if (options.fullScreen && state.isFullScreen) {
win.setFullScreen(true);
}
win.on('resize', handleStateChange);
win.on('move', handleStateChange);
win.on('close', handleClose);
win.on('closed', handleClosed);
winRef = win;
}
function unmanage() {
if (winRef) {
winRef.removeListener('resize', handleStateChange);
winRef.removeListener('move', handleStateChange);
clearTimeout(stateChangeTimer);
winRef.removeListener('close', handleClose);
winRef.removeListener('closed', handleClosed);
winRef = undefined;
}
}
function saveLastUrlHash() {
if (winRef) {
const { hash } = new URL(winRef.webContents.getURL());
state.urlHash = hash;
}
}
function clearLastUrlHash() {
state.urlHash = '';
}
state = store.get(STORE_KEY) as State;
validateState();
return {
get x() { return state.x; },
get y() { return state.y; },
get width() { return state.width; },
get height() { return state.height; },
get displayBounds() { return state.displayBounds; },
get isMaximized() { return state.isMaximized; },
get isFullScreen() { return state.isFullScreen; },
get urlHash() { return state.urlHash || ''; },
unmanage,
manage,
resetStateToDefault,
saveLastUrlHash,
clearLastUrlHash,
};
}
export default windowStateKeeper;