2019-11-11 15:53:39 +09:00
|
|
|
import * as path from 'path';
|
|
|
|
import * as url from 'url';
|
|
|
|
|
|
|
|
import { app, BrowserWindow, screen, ipcMain, IpcMainEvent } from 'electron';
|
|
|
|
import windowStateKeeper from 'electron-window-state';
|
|
|
|
import { EventEmitter } from 'events';
|
|
|
|
|
|
|
|
import { now } from '../util/now';
|
|
|
|
import { registerWindowStateChangedEvents } from '../lib/window-state';
|
|
|
|
import {
|
|
|
|
ElectronAppChannel,
|
|
|
|
ElectronBrowserWindowChannel,
|
|
|
|
ElectronWebContentsChannel
|
|
|
|
} from '@ucap-webmessenger/electron-core';
|
|
|
|
|
|
|
|
export class AppWindow {
|
|
|
|
private window: BrowserWindow | null = null;
|
|
|
|
|
|
|
|
private eventEmitter = new EventEmitter();
|
|
|
|
|
|
|
|
// tslint:disable-next-line: variable-name
|
|
|
|
private _loadTime: number | null = null;
|
|
|
|
// tslint:disable-next-line: variable-name
|
|
|
|
private _rendererReadyTime: number | null = null;
|
|
|
|
|
2019-12-20 10:15:48 +09:00
|
|
|
private minWidth = 1160;
|
|
|
|
private minHeight = 800;
|
2019-11-11 15:53:39 +09:00
|
|
|
|
2019-11-18 15:02:24 +09:00
|
|
|
public constructor(private appIconPath: string) {
|
2019-11-11 15:53:39 +09:00
|
|
|
const savedWindowState = windowStateKeeper({
|
|
|
|
defaultWidth: this.minWidth,
|
|
|
|
defaultHeight: this.minHeight
|
|
|
|
});
|
|
|
|
|
|
|
|
const windowOptions: Electron.BrowserWindowConstructorOptions = {
|
|
|
|
x: savedWindowState.x,
|
|
|
|
y: savedWindowState.y,
|
|
|
|
width: savedWindowState.width,
|
|
|
|
height: savedWindowState.height,
|
|
|
|
minWidth: this.minWidth,
|
|
|
|
minHeight: this.minHeight,
|
|
|
|
center: true,
|
|
|
|
// This fixes subpixel aliasing on Windows
|
|
|
|
// See https://github.com/atom/atom/commit/683bef5b9d133cb194b476938c77cc07fd05b972
|
|
|
|
backgroundColor: '#fff',
|
|
|
|
webPreferences: {
|
|
|
|
// Disable auxclick event
|
|
|
|
// See https://developers.google.com/web/updates/2016/10/auxclick
|
|
|
|
disableBlinkFeatures: 'Auxclick',
|
|
|
|
// Enable, among other things, the ResizeObserver
|
|
|
|
experimentalFeatures: true,
|
|
|
|
nodeIntegration: true
|
|
|
|
},
|
|
|
|
acceptFirstMouse: true,
|
2019-11-18 15:02:24 +09:00
|
|
|
icon: this.appIconPath
|
2019-11-11 15:53:39 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
if (__DARWIN__) {
|
|
|
|
windowOptions.titleBarStyle = 'hidden';
|
|
|
|
} else if (__WIN32__) {
|
|
|
|
windowOptions.frame = false;
|
|
|
|
} else if (__LINUX__) {
|
|
|
|
}
|
|
|
|
|
|
|
|
this.window = new BrowserWindow(windowOptions);
|
|
|
|
savedWindowState.manage(this.window);
|
|
|
|
|
2019-12-04 13:51:50 +09:00
|
|
|
let quitting = true;
|
2019-11-11 15:53:39 +09:00
|
|
|
app.on(ElectronAppChannel.BeforeQuit, () => {
|
|
|
|
quitting = true;
|
|
|
|
});
|
|
|
|
|
|
|
|
ipcMain.on(ElectronAppChannel.WillQuit, (event: IpcMainEvent) => {
|
|
|
|
quitting = true;
|
|
|
|
event.returnValue = true;
|
|
|
|
});
|
|
|
|
|
|
|
|
// on macOS, when the user closes the window we really just hide it. This
|
|
|
|
// lets us activate quickly and keep all our interesting logic in the
|
|
|
|
// renderer.
|
|
|
|
if (__DARWIN__) {
|
|
|
|
this.window.on(ElectronBrowserWindowChannel.Close, e => {
|
|
|
|
if (!quitting) {
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
});
|
2019-11-18 16:49:41 +09:00
|
|
|
} else if (__WIN32__) {
|
|
|
|
this.window.on(ElectronBrowserWindowChannel.Minimize, e => {
|
|
|
|
if (!quitting) {
|
|
|
|
e.preventDefault();
|
2019-12-04 18:10:22 +09:00
|
|
|
this.window.minimize();
|
2019-11-18 16:49:41 +09:00
|
|
|
}
|
|
|
|
});
|
|
|
|
this.window.on(ElectronBrowserWindowChannel.Close, e => {
|
2019-12-04 13:51:50 +09:00
|
|
|
// if (!quitting) {
|
|
|
|
e.preventDefault();
|
|
|
|
this.window.hide();
|
|
|
|
// }
|
|
|
|
});
|
|
|
|
this.window.on(ElectronBrowserWindowChannel.Focus, e => {
|
|
|
|
this.window.flashFrame(false);
|
2019-11-18 16:49:41 +09:00
|
|
|
});
|
2019-11-11 15:53:39 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
if (__WIN32__) {
|
|
|
|
// workaround for known issue with fullscreen-ing the app and restoring
|
|
|
|
// is that some Chromium API reports the incorrect bounds, so that it
|
|
|
|
// will leave a small space at the top of the screen on every other
|
|
|
|
// maximize
|
|
|
|
//
|
|
|
|
// adapted from https://github.com/electron/electron/issues/12971#issuecomment-403956396
|
|
|
|
//
|
|
|
|
// can be tidied up once https://github.com/electron/electron/issues/12971
|
|
|
|
// has been confirmed as resolved
|
|
|
|
this.window.once(ElectronBrowserWindowChannel.ReadyToShow, () => {
|
|
|
|
this.window.on(ElectronBrowserWindowChannel.Unmaximize, () => {
|
|
|
|
setTimeout(() => {
|
|
|
|
const bounds = this.window.getBounds();
|
|
|
|
bounds.width += 1;
|
|
|
|
this.window.setBounds(bounds);
|
|
|
|
bounds.width -= 1;
|
|
|
|
this.window.setBounds(bounds);
|
|
|
|
}, 5);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public load(): void {
|
|
|
|
let startLoad = 0;
|
|
|
|
|
|
|
|
this.window.webContents.once(
|
|
|
|
ElectronWebContentsChannel.DidStartLoading,
|
|
|
|
() => {
|
|
|
|
this._rendererReadyTime = null;
|
|
|
|
this._loadTime = null;
|
|
|
|
|
|
|
|
startLoad = now();
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
this.window.webContents.once(
|
|
|
|
ElectronWebContentsChannel.DidFinishLoad,
|
|
|
|
() => {
|
|
|
|
this.window.webContents.setVisualZoomLevelLimits(1, 1);
|
|
|
|
|
|
|
|
if (process.env.NODE_ENV === 'development') {
|
|
|
|
this.window.webContents.openDevTools();
|
|
|
|
}
|
|
|
|
|
|
|
|
this._loadTime = now() - startLoad;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
this.window.webContents.on(ElectronWebContentsChannel.DidFailLoad, () => {
|
|
|
|
this.window.webContents.openDevTools();
|
|
|
|
this.window.show();
|
|
|
|
});
|
|
|
|
|
|
|
|
registerWindowStateChangedEvents(this.window);
|
|
|
|
|
|
|
|
if (__DEV__) {
|
|
|
|
this.window.loadURL('http://localhost:4200');
|
|
|
|
} else {
|
|
|
|
this.window.loadURL(
|
|
|
|
url.format({
|
|
|
|
pathname: path.join(
|
|
|
|
__dirname,
|
|
|
|
'..',
|
|
|
|
'ucap-webmessenger-app/index.html'
|
|
|
|
),
|
|
|
|
protocol: 'file:',
|
|
|
|
slashes: true
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Is the page loaded and has the renderer signalled it's ready? */
|
|
|
|
private get rendererLoaded(): boolean {
|
|
|
|
return !!this.loadTime && !!this.rendererReadyTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
public onClose(fn: () => void) {
|
|
|
|
this.window.on(ElectronBrowserWindowChannel.Closed, fn);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Register a function to call when the window is done loading. At that point
|
|
|
|
* the page has loaded and the renderer has signalled that it is ready.
|
|
|
|
*/
|
|
|
|
public onDidLoad(fn: () => void): EventEmitter {
|
|
|
|
return this.eventEmitter.on('did-load', fn);
|
|
|
|
}
|
|
|
|
|
|
|
|
public isMinimized() {
|
|
|
|
return this.window.isMinimized();
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Is the window currently visible? */
|
|
|
|
public isVisible() {
|
|
|
|
return this.window.isVisible();
|
|
|
|
}
|
|
|
|
|
|
|
|
public restore() {
|
|
|
|
this.window.restore();
|
|
|
|
}
|
|
|
|
|
|
|
|
public focus() {
|
|
|
|
this.window.focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Show the window. */
|
|
|
|
public show() {
|
|
|
|
this.window.show();
|
|
|
|
}
|
|
|
|
|
2019-11-18 15:02:24 +09:00
|
|
|
public hide() {
|
|
|
|
this.window.hide();
|
|
|
|
}
|
|
|
|
|
2019-11-11 15:53:39 +09:00
|
|
|
/**
|
|
|
|
* Get the time (in milliseconds) spent loading the page.
|
|
|
|
*
|
|
|
|
* This will be `null` until `onDidLoad` is called.
|
|
|
|
*/
|
|
|
|
public get loadTime(): number | null {
|
|
|
|
return this._loadTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the time (in milliseconds) elapsed from the renderer being loaded to it
|
|
|
|
* signaling it was ready.
|
|
|
|
*
|
|
|
|
* This will be `null` until `onDidLoad` is called.
|
|
|
|
*/
|
|
|
|
public get rendererReadyTime(): number | null {
|
|
|
|
return this._rendererReadyTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
public destroy() {
|
|
|
|
this.window.destroy();
|
|
|
|
}
|
|
|
|
|
|
|
|
public get browserWindow(): BrowserWindow | null {
|
|
|
|
return this.window;
|
|
|
|
}
|
|
|
|
}
|