Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/replay/backend/overlays/BaseOverlay.ts
1030 views
1
import { BrowserView, BrowserWindow } from 'electron';
2
import IRectangle from '~shared/interfaces/IRectangle';
3
import Application from '~backend/Application';
4
import generateContextMenu from '~backend/menus/generateContextMenu';
5
import Window from '../models/Window';
6
import Rectangle = Electron.Rectangle;
7
8
interface IOptions {
9
name: string;
10
bounds?: IRectangle;
11
calcBounds?: (bounds: IRectangle) => IRectangle;
12
customHide?: boolean;
13
webPreferences?: Electron.WebPreferences;
14
onWindowBoundsUpdate?: () => void;
15
maxHeight?: number;
16
}
17
18
export default class BaseOverlay {
19
public name: string;
20
public browserWindow: BrowserWindow;
21
public browserView: BrowserView;
22
public visible = false;
23
24
public maxHeight = 500;
25
protected lastHeight = 0;
26
protected hasNewHeight = true;
27
28
private readonly calcBounds: (bounds: IRectangle) => IRectangle;
29
private isReady: Promise<void>;
30
31
public constructor(options: IOptions) {
32
const { name, bounds, calcBounds, webPreferences, maxHeight } = options;
33
this.browserView = new BrowserView({
34
webPreferences: {
35
nodeIntegration: true,
36
contextIsolation: false,
37
enableRemoteModule: true,
38
...webPreferences,
39
},
40
});
41
42
this.browserView.setAutoResize({
43
height: true,
44
width: true,
45
});
46
47
this.calcBounds = calcBounds;
48
this.name = name;
49
this.maxHeight = maxHeight ?? 500;
50
51
if (bounds) {
52
this.browserView.setBounds({
53
x: bounds.x ?? 0,
54
y: bounds.y ?? 0,
55
height: bounds.height ?? this.maxHeight,
56
width: bounds.width,
57
});
58
}
59
this.isReady = this.load();
60
}
61
62
public get webContents() {
63
return this.browserView.webContents;
64
}
65
66
public get id() {
67
return this.webContents.id;
68
}
69
70
public show(
71
browserWindow: BrowserWindow,
72
options: { focus?: boolean; rect?: IRectangle },
73
...args: any[]
74
) {
75
const { focus = true, rect } = options;
76
this.browserWindow = browserWindow;
77
78
this.webContents.send('will-show', ...args);
79
if (this.visible) {
80
this.rearrange(rect);
81
return;
82
}
83
84
// remove first so we can add back on top
85
browserWindow.addBrowserView(this.browserView);
86
this.visible = true;
87
this.rearrange(rect);
88
if (focus) {
89
this.webContents.focus();
90
}
91
}
92
93
public send(channel: string, ...args: any[]) {
94
this.webContents.send(channel, ...args);
95
}
96
97
public hide() {
98
if (!this.browserWindow || this.browserWindow.isDestroyed()) return;
99
if (!this.visible) return;
100
101
this.browserWindow.removeBrowserView(this.browserView);
102
103
this.visible = false;
104
}
105
106
public destroy() {
107
this.browserView = null;
108
}
109
110
protected getHeight(): Promise<number> {
111
return this.webContents.executeJavaScript(`document.querySelector('.Page').offsetHeight`);
112
}
113
114
protected async adjustHeight() {
115
const height = await this.getHeight();
116
117
this.hasNewHeight = height !== this.lastHeight;
118
this.lastHeight = height;
119
}
120
121
protected rearrange(rect: IRectangle = {}) {
122
if (!this.visible) return;
123
124
// put on top
125
this.browserWindow.addBrowserView(this.browserView);
126
const newRect = roundifyRectangle(this.calcBounds ? this.calcBounds(rect) : rect);
127
const current = this.browserView.getBounds();
128
if (
129
current.height === newRect.height &&
130
current.width === newRect.width &&
131
current.x === newRect.x &&
132
current.y === newRect.y
133
) {
134
return;
135
}
136
137
this.browserView.setBounds(newRect as Rectangle);
138
}
139
140
private async maximize() {
141
const bounds = await Window.current.getAvailableBounds();
142
// inset 10%
143
const inset = Math.round(bounds.width * 0.1);
144
bounds.width -= inset;
145
bounds.x += Math.round(inset / 2);
146
bounds.y += 50;
147
bounds.height -= 50 + 28;
148
this.browserView.setBounds(bounds);
149
}
150
151
private async load() {
152
await this.webContents.loadURL(Application.instance.getPageUrl(this.name));
153
this.webContents.on('ipc-message', (e, message) => {
154
if (message === 'resize-height') {
155
this.adjustHeight();
156
}
157
if (message === 'zoomin-overlay') {
158
this.maximize();
159
}
160
if (message === 'zoomout-overlay') {
161
this.rearrange(this.browserView.getBounds());
162
}
163
});
164
165
// resize the BrowserView's height when the toolbar height changes
166
await this.webContents.executeJavaScript(`
167
const {ipcRenderer} = require('electron');
168
const resizeObserver = new ResizeObserver(() => {
169
ipcRenderer.send('resize-height');
170
});
171
const elem = document.querySelector('.Page');
172
resizeObserver.observe(elem);
173
`);
174
175
if (process.env.NODE_ENV === 'development') {
176
this.webContents.on('context-menu', (e, params) => {
177
generateContextMenu(params, this.webContents).popup();
178
});
179
}
180
}
181
}
182
183
export const roundifyRectangle = (rect: IRectangle): IRectangle => {
184
const newRect: any = { ...rect };
185
Object.keys(newRect).forEach(key => {
186
if (!Number.isNaN(newRect[key])) newRect[key] = Math.round(newRect[key]);
187
});
188
return newRect;
189
};
190
191