electron-event-flux
Version:
Redux store which synchronizes between instances in multiple process
334 lines (285 loc) • 10.5 kB
text/typescript
import MultiWinStore from './MultiWinStore';
import ElectronWindowState, { IWinState } from './ElectronWindowState';
import { format as formatUrl } from 'url';
import * as path from 'path';
import { app, BrowserWindow, screen } from 'electron';
import IStorage from './storage/IStorage';
import { IWinProps, IWinParams } from './IMultiWinStore';
const isDevelopment = process.env.NODE_ENV !== 'production'
export class WindowManager {
windows: { clientId: string, window: BrowserWindow }[] = [];
winHandler: any;
winSize = 1;
constructor(winHandler: MultiWinCacheStore) {
this.windows = [];
this.winHandler = winHandler;
app.whenReady().then(() => this.ensureWindows());
// this.ensureWindows();
}
genClientId() {
let clientId = 'win' + Math.floor(Math.random() * 10000);
return clientId;
}
createWin(clientId: string) {
return this.winHandler.createNativeWindow(clientId);
}
ensureWindows() {
if (!this.windows) return;
while (this.windows.length < this.winSize) {
let clientId = this.genClientId();
this.windows.push({ clientId, window: this.createWin(clientId) });
}
}
getWin(): { clientId: string, window: BrowserWindow } | undefined {
if (!this.windows) return undefined;
let winInfo = this.windows.shift();
this.ensureWindows();
return winInfo;
}
dispose() {
this.windows.forEach(({ window }) => {
if (!window.isDestroyed()) {
window.close()
}
});
this.windows = [];
}
}
interface IClientCacheInfo {
parentId?: string;
clientId: string;
name?: string;
groups?: string[];
url: string;
winState?: IWinState;
}
class MultiWinCacheStore extends MultiWinStore {
clientInfoMap: { [clientId: string]: IClientCacheInfo } = {};
willQuit = false;
windowManager?: WindowManager;
init() {
this.loadClients();
}
loadClients() {
this.willQuit = false;
let clients = this.getStorage()!.get('clients');
if (!clients || clients.length === 0) {
clients = this.getDefaultClients();
}
if (!this.windowManager) {
this.windowManager = new WindowManager(this);
}
app.whenReady().then(() => {
clients.forEach((item: IClientCacheInfo) => {
this.createWinForClientId({ path: item.url, groups: item.groups, name: item.name }, item.clientId, item.parentId, item.winState!)
});
});
}
saveClients() {
let clients = this.clientIds.map(id => ({
clientId: id, ...this.clientInfoMap[id], name: this.clientIdNameMap[id], groups: this.groupsMap[id],
}));
this.getStorage()!.set('clients', clients);
}
getDefaultClients(): IClientCacheInfo[] {
return [{ clientId: 'mainClient', url: '/', winState: { isMaximized: true } }];
}
getStorage(): IStorage | null {
console.error('You need inherit MultiWinCacheStore and implement getStorage');
return null;
}
_removeClientId(clientId: string) {
super._removeClientId(clientId);
delete this.clientInfoMap[clientId];
}
onDidWinClose(clientId: string) {
(this._appStore as any).mainClient.sendWinMsg(clientId, { action: 'did-close' });
}
closeWin(clientId: string) {
let win = this.clientWins[clientId] as BrowserWindow;
if (win && !win.isDestroyed()) win.close();
}
saveWinState(clientId: string, winState: IWinState) {
this.clientInfoMap[clientId].winState = winState;
this.saveClients();
}
// 当要创建的窗口有clientId时
createWinForClientId(winProps: IWinProps, clientId: string, parentClientId: string | undefined, params: IWinParams): string | null {
return this._createElectronWin(winProps, clientId, parentClientId, params);
}
// 创建一个全新的窗口,使用自生成的clientId
createWin(winProps: IWinProps | string, parentClientId: string, params: IWinParams): string | null {
return this._createWinForElectron(winProps, parentClientId, params);
}
_createWinForElectron(winProps: IWinProps | string, parentClientId: string, params: IWinParams): string | null {
winProps = this._parseWinProps(winProps);
if (params && params.x == null && params.y == null) {
params.width = params.width || 800;
params.height = params.height || 600;
if (parentClientId && this.clientWins[parentClientId]) {
let window = this.clientWins[parentClientId] as BrowserWindow;
// let window = (this._appStore as any).mainClient.getWindowByClientId(parentClientId);
let bounds = params.useContentSize ? window.getContentBounds() : window.getBounds();
params.x = bounds.x + bounds.width / 2 - params.width / 2;
params.y = bounds.y + bounds.height / 2 - params.height / 2;
} else {
let screenSize = screen.getPrimaryDisplay().size;
params.x = screenSize.width / 2 - params.width / 2;
params.y = screenSize.height / 2 - params.height / 2;
}
}
let clientId = null;
try {
clientId = this._createElectronWin(winProps, null, parentClientId, params);
} catch(err) {
console.error(err, err.stack);
}
return clientId;
}
_createElectronWin(url: string | IWinProps, clientId: string | null, parentId: string | undefined, params: IWinState): string | null {
if (clientId && this.clientIds.indexOf(clientId) !== -1) {
return null;
}
let winProps = this._parseWinProps(url);
let winState = new ElectronWindowState(null, params, null);
let winInfo = this._getElectronWinFromCache(winProps.path!, clientId, parentId, winState.state);
if (!clientId) clientId = winInfo.clientId;
let win = winInfo.win;
this._addWinProps(clientId, win, winProps);
this.clientInfoMap[clientId] = { url: winProps.path!, clientId, parentId, winState: winState.state };
winState.onSave = (state) => {
this.saveWinState(clientId!, state);
};
// this.clientStateMap[clientId] = winState.state;
winState.manage(win);
this.saveClients(); // Save clients into Storage
win.on('closed', this.onCloseWin.bind(this, clientId));
return clientId;
}
_getElectronWinFromCache(url: string, clientId: string | null, parentId: string | undefined, params: IWinParams) {
// return createMainWindow(url, clientId, params);
let win: BrowserWindow;
if (clientId) {
win = this.createMainWindow(url, clientId, parentId, params);
} else {
let winInfo = this.windowManager!.getWin();
if (!winInfo) {
clientId = this._genClientId();
win = this.createMainWindow(url, clientId, parentId, params);
} else {
clientId = winInfo.clientId;
win = winInfo.window;
// this._appStore.mainClient.sendMessage(win, { action: 'change-props', url, parentId });
(this._appStore as any).mainClient.changeClientAction(clientId, { url, parentId });
let setBoundsFunc: 'setContentBounds' | 'setBounds' = params.useContentSize ? 'setContentBounds' : 'setBounds';
let x = Math.floor(params.x || 0);
let y = Math.floor(params.y || 0);
let width = Math.floor(params.width || 800), height = Math.floor(params.height || 600);
if (params.minWidth && params.minHeight) {
win.setMinimumSize(Math.floor(params.minWidth), Math.floor(params.minHeight));
}
if (params.maxWidth && params.maxHeight) {
win.setMaximumSize(Math.floor(params.maxWidth), Math.floor(params.maxHeight));
}
if (params.title) {
win.setTitle(params.title);
}
win[setBoundsFunc]({
x, y, width, height,
});
win[setBoundsFunc]({
x, y, width, height,
});
setTimeout(() => {
win[setBoundsFunc]({x, y, width, height})
win.show();
}, 0);
}
}
return { clientId, win };
}
createNativeWindow(clientId: string, url = 'empty', parentId = '', params: IWinParams) {
if (params == null) {
params = {
x: 0, y: 0,
width: 1280, height: 800,
useContentSize: true,
};
}
const window = new BrowserWindow({
show: false,
x: Math.floor(params.x || 0), y: Math.floor(params.y || 0),
width: params.width, height: params.height,
minWidth: params.minWidth || params.width, minHeight: params.minHeight || params.height,
maxWidth: params.maxWidth, maxHeight: params.maxHeight,
title: params.title,
useContentSize: params.useContentSize,
});
if (isDevelopment) {
window.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}?url=${url}&clientId=${clientId}&parentId=${parentId}`);
// window.webContents.openDevTools();
}
else {
window.loadURL(formatUrl({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file',
slashes: true,
query: { url: url, clientId, parentId },
}));
}
// window.webContents.openDevTools();
window.webContents.on('devtools-opened', () => {
window.focus();
setImmediate(() => {
window.focus();
});
});
return window;
}
createMainWindow(url: string, clientId: string, parentId: string | undefined, params: any = {}) {
let window = this.createNativeWindow(clientId, url, parentId, params);
window.on('ready-to-show', function() {
window.show();
});
return window;
}
onChangeAction(clientId: string, action: string) {
if (this.clientInfoMap[clientId]) {
this.clientInfoMap[clientId].url = action;
this.saveClients();
}
}
onCloseMainClient() {
this.willQuit = true;
this.closeAllWindows();
}
onCloseWin(clientId: string) {
if (clientId === 'mainClient') {
this.onCloseMainClient();
}
this.onDidWinClose && this.onDidWinClose(clientId!);
let index = this.clientIds.indexOf(clientId!);
if (index !== -1) {
this.clientIds.splice(index, 1);
}
if (!this.willQuit) this.saveClients();
this._removeClientId(clientId!);
}
closeAllWindows() {
super.closeAllWindows();
// 关掉隐藏窗口
if (this.windowManager) {
this.windowManager.dispose();
this.windowManager = undefined;
}
}
activeWin(clientId: string) {
const win = this.clientWins[clientId] as BrowserWindow;
if (win) {
win.moveTop();
// win && win.minimize();
win.focus();
}
}
}
export default MultiWinCacheStore;