nexfep 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +1 -46
- package/index.js +1 -387
- package/package.json +4 -2
- package/src/WindowManager.d.ts +46 -0
- package/src/WindowManager.js +387 -0
package/index.d.ts
CHANGED
|
@@ -1,46 +1 @@
|
|
|
1
|
-
|
|
2
|
-
declare class Window {
|
|
3
|
-
window: BrowserWindow;
|
|
4
|
-
webview: Webview;
|
|
5
|
-
isOpen: boolean;
|
|
6
|
-
isShow: boolean;
|
|
7
|
-
isDecorated: boolean;
|
|
8
|
-
id: number;
|
|
9
|
-
private myPool;
|
|
10
|
-
constructor(window: BrowserWindow, webview: Webview, id: number, myPool: WindowPool);
|
|
11
|
-
setDecorated(isDecorated: boolean): void;
|
|
12
|
-
setTitle(title: string): void;
|
|
13
|
-
resizable(resizable: boolean): void;
|
|
14
|
-
maximize(): void;
|
|
15
|
-
minimize(): void;
|
|
16
|
-
unMaximize(): void;
|
|
17
|
-
unMinimize(): void;
|
|
18
|
-
openDevTools(): void;
|
|
19
|
-
closeDevTools(): void;
|
|
20
|
-
hide(): void;
|
|
21
|
-
show(): void;
|
|
22
|
-
close(): void;
|
|
23
|
-
loadURL(url: string): Promise<void>;
|
|
24
|
-
loadHTML(html: string): Promise<void>;
|
|
25
|
-
}
|
|
26
|
-
declare class WindowPool {
|
|
27
|
-
onCustomMessage: (window: Window, data: string) => void;
|
|
28
|
-
private app;
|
|
29
|
-
private windows;
|
|
30
|
-
private handlers;
|
|
31
|
-
private injectCount;
|
|
32
|
-
private windowCount;
|
|
33
|
-
private freeWindowCount;
|
|
34
|
-
global: Map<string, any>;
|
|
35
|
-
constructor(WindowsWebview2UserDataFolder?: string);
|
|
36
|
-
__injectCode(window: Window, code: string): Promise<void>;
|
|
37
|
-
__injectControlFunctions(windowObj: Window): Promise<void>;
|
|
38
|
-
__createNewWindowObj(): Promise<Window>;
|
|
39
|
-
createWindow(isShow?: boolean, isDecorated?: boolean): Promise<Window>;
|
|
40
|
-
closeWindow(window: Window): Promise<void>;
|
|
41
|
-
closePool(): Promise<void>;
|
|
42
|
-
handle(event: string, callback: (data: any) => any): Promise<void>;
|
|
43
|
-
unhandle(event: string, callback: (data: any) => any): Promise<void>;
|
|
44
|
-
mainloop(): void;
|
|
45
|
-
}
|
|
46
|
-
export { WindowPool, Window };
|
|
1
|
+
export * from './src/WindowManager.js';
|
package/index.js
CHANGED
|
@@ -1,387 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import os from 'os';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import fs from 'fs';
|
|
5
|
-
class Window {
|
|
6
|
-
window;
|
|
7
|
-
webview;
|
|
8
|
-
isOpen;
|
|
9
|
-
isShow;
|
|
10
|
-
isDecorated;
|
|
11
|
-
id;
|
|
12
|
-
myPool;
|
|
13
|
-
constructor(window, webview, id, myPool) {
|
|
14
|
-
this.myPool = myPool;
|
|
15
|
-
this.window = window;
|
|
16
|
-
this.webview = webview;
|
|
17
|
-
this.isOpen = false;
|
|
18
|
-
this.isShow = false;
|
|
19
|
-
this.isDecorated = false;
|
|
20
|
-
this.id = id;
|
|
21
|
-
}
|
|
22
|
-
setDecorated(isDecorated) {
|
|
23
|
-
this.window.setDecorations(isDecorated);
|
|
24
|
-
this.isDecorated = isDecorated;
|
|
25
|
-
}
|
|
26
|
-
setTitle(title) {
|
|
27
|
-
this.window.setTitle(title);
|
|
28
|
-
}
|
|
29
|
-
resizable(resizable) {
|
|
30
|
-
this.window.setResizable(resizable);
|
|
31
|
-
}
|
|
32
|
-
maximize() {
|
|
33
|
-
this.window.setMaximized(true);
|
|
34
|
-
}
|
|
35
|
-
minimize() {
|
|
36
|
-
this.window.setMinimized(true);
|
|
37
|
-
}
|
|
38
|
-
unMaximize() {
|
|
39
|
-
this.window.setMaximized(false);
|
|
40
|
-
}
|
|
41
|
-
unMinimize() {
|
|
42
|
-
this.window.setMinimized(false);
|
|
43
|
-
}
|
|
44
|
-
openDevTools() {
|
|
45
|
-
this.webview.openDevtools();
|
|
46
|
-
}
|
|
47
|
-
closeDevTools() {
|
|
48
|
-
this.webview.closeDevtools();
|
|
49
|
-
}
|
|
50
|
-
hide() {
|
|
51
|
-
this.window.hide();
|
|
52
|
-
this.isShow = false;
|
|
53
|
-
}
|
|
54
|
-
show() {
|
|
55
|
-
this.window.show();
|
|
56
|
-
this.isShow = true;
|
|
57
|
-
}
|
|
58
|
-
close() {
|
|
59
|
-
this.myPool.closeWindow(this);
|
|
60
|
-
}
|
|
61
|
-
async loadURL(url) {
|
|
62
|
-
await this.webview.loadUrl(url);
|
|
63
|
-
await this.myPool.__injectControlFunctions(this);
|
|
64
|
-
}
|
|
65
|
-
async loadHTML(html) {
|
|
66
|
-
await this.webview.loadHtml(html);
|
|
67
|
-
await this.myPool.__injectControlFunctions(this);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
class WindowPool {
|
|
71
|
-
onCustomMessage;
|
|
72
|
-
app;
|
|
73
|
-
windows;
|
|
74
|
-
handlers;
|
|
75
|
-
injectCount;
|
|
76
|
-
windowCount;
|
|
77
|
-
freeWindowCount;
|
|
78
|
-
global;
|
|
79
|
-
constructor(WindowsWebview2UserDataFolder = path.join(process.env.LOCALAPPDATA || os.homedir(), 'NexfepDevelopment.webview2-data')) {
|
|
80
|
-
if (os.platform() === 'win32') {
|
|
81
|
-
// 设置 WebView2 用户数据目录(仅 Windows 需要此配置)
|
|
82
|
-
const userDataDir = WindowsWebview2UserDataFolder;
|
|
83
|
-
if (!fs.existsSync(userDataDir)) {
|
|
84
|
-
fs.mkdirSync(userDataDir, { recursive: true });
|
|
85
|
-
}
|
|
86
|
-
process.env.WEBVIEW2_USER_DATA_FOLDER = userDataDir;
|
|
87
|
-
}
|
|
88
|
-
this.onCustomMessage = (window, data) => {
|
|
89
|
-
console.log('Get Custom Message:', data, "from window", window.id);
|
|
90
|
-
};
|
|
91
|
-
this.handlers = new Map();
|
|
92
|
-
this.app = new Application();
|
|
93
|
-
this.injectCount = 0;
|
|
94
|
-
this.windows = [];
|
|
95
|
-
this.windowCount = 0;
|
|
96
|
-
this.freeWindowCount = 0;
|
|
97
|
-
this.global = new Map();
|
|
98
|
-
}
|
|
99
|
-
async __injectCode(window, code) {
|
|
100
|
-
await window.webview.evaluateScript(code);
|
|
101
|
-
}
|
|
102
|
-
async __injectControlFunctions(windowObj) {
|
|
103
|
-
const INJECT_CSS = `
|
|
104
|
-
[nexfep-area-drag],
|
|
105
|
-
[nexfep-area-drag] * {
|
|
106
|
-
-webkit-app-region: drag;
|
|
107
|
-
app-region: drag;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
[nexfep-element-drag] {
|
|
111
|
-
-webkit-app-region: drag;
|
|
112
|
-
app-region: drag;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
[nexfep-no-drag],
|
|
116
|
-
[nexfep-no-drag] * {
|
|
117
|
-
-webkit-app-region: no-drag !important;
|
|
118
|
-
app-region: no-drag !important;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
[nexfep-auto-drag],
|
|
122
|
-
[nexfep-auto-drag] * {
|
|
123
|
-
-webkit-app-region: drag;
|
|
124
|
-
app-region: drag;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
[nexfep-auto-drag] button,
|
|
128
|
-
[nexfep-auto-drag] input,
|
|
129
|
-
[nexfep-auto-drag] select,
|
|
130
|
-
[nexfep-auto-drag] textarea,
|
|
131
|
-
[nexfep-auto-drag] a {
|
|
132
|
-
-webkit-app-region: no-drag;
|
|
133
|
-
app-region: no-drag;
|
|
134
|
-
}
|
|
135
|
-
`;
|
|
136
|
-
const INJECT_CODE = `
|
|
137
|
-
// 事件监听
|
|
138
|
-
window.addEventListener("beforeunload", () => {
|
|
139
|
-
const MessageBody = { type: 'NexfepBeforeUnload' }
|
|
140
|
-
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// 窗口控制函数
|
|
144
|
-
window.close = () => {
|
|
145
|
-
const MessageBody = { type: 'NexfepCloseWindow' }
|
|
146
|
-
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
window.minimize = () => {
|
|
150
|
-
const MessageBody = { type: 'NexfepMinimizeWindow' }
|
|
151
|
-
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
window.unminimize = () => {
|
|
155
|
-
const MessageBody = { type: 'NexfepUnMinimizeWindow' }
|
|
156
|
-
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
window.maximize = () => {
|
|
160
|
-
const MessageBody = { type: 'NexfepMaximizeWindow' }
|
|
161
|
-
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
window.unmaximize = () => {
|
|
165
|
-
const MessageBody = { type: 'NexfepUnMaximizeWindow' }
|
|
166
|
-
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
167
|
-
};
|
|
168
|
-
window.setTitle = (title) => {
|
|
169
|
-
const MessageBody = { type: 'NexfepSetTitle', title: title }
|
|
170
|
-
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
window.openDevTools = () => {
|
|
174
|
-
const MessageBody = { type: 'NexfepOpenDevTools' }
|
|
175
|
-
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
window.closeDevTools = () => {
|
|
179
|
-
const MessageBody = { type: 'NexfepCloseDevTools' }
|
|
180
|
-
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
window.postMessage = (data) => {
|
|
184
|
-
const MessageBody = { type: 'CustomMessage', data: data }
|
|
185
|
-
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
window.eventCount = 0;
|
|
189
|
-
|
|
190
|
-
window.invoke = async (event, data = null) => {
|
|
191
|
-
const MessageBody = { type: 'NexfepInvoke', eventId: [${this.injectCount}, ++window.eventCount], event: event, data: data }
|
|
192
|
-
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
193
|
-
return new Promise((resolve, reject) => {
|
|
194
|
-
window.addEventListener("nexfep-invoke-result-"+${this.injectCount}+"-"+window.eventCount, (event) => {
|
|
195
|
-
window.removeEventListener("nexfep-invoke-result-"+${this.injectCount}+"-"+window.eventCount, event);
|
|
196
|
-
if(event.detail){
|
|
197
|
-
resolve(event.detail);
|
|
198
|
-
}else{
|
|
199
|
-
resolve();
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
window.setGlobal = (name, value) => {
|
|
206
|
-
const MessageBody = { type: 'NexfepSetGlobal', name: name, value: value }
|
|
207
|
-
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
window.getGlobal = (name) => {
|
|
211
|
-
const MessageBody = { type: 'NexfepGetGlobal', eventId: [${this.injectCount}, ++window.eventCount], name: name }
|
|
212
|
-
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
213
|
-
return new Promise((resolve, reject) => {
|
|
214
|
-
window.addEventListener("nexfep-get-global-result-"+${this.injectCount}+"-"+window.eventCount, (event) => {
|
|
215
|
-
window.removeEventListener("nexfep-get-global-result-"+${this.injectCount}+"-"+window.eventCount, event);
|
|
216
|
-
if(event.detail){
|
|
217
|
-
resolve(event.detail);
|
|
218
|
-
}else{
|
|
219
|
-
resolve();
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
window.broadcast = (name, data = null) => {
|
|
226
|
-
const MessageBody = { type: 'NexfepBroadcast', name: name, data: data }
|
|
227
|
-
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
window.id = ${windowObj.id};
|
|
231
|
-
|
|
232
|
-
window.tell = (to, message, data = null) => {
|
|
233
|
-
const MessageBody = { type: 'NexfepTell', to: to, message: message, data: data }
|
|
234
|
-
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// 注入 CSS 样式
|
|
238
|
-
const style = document.createElement('style');
|
|
239
|
-
style.textContent = \`${INJECT_CSS}\`;
|
|
240
|
-
document.head.appendChild(style);
|
|
241
|
-
|
|
242
|
-
// 标记加载完成
|
|
243
|
-
window.isNexfepLoadDone = true;
|
|
244
|
-
|
|
245
|
-
// 触发加载完成事件
|
|
246
|
-
window.dispatchEvent(new CustomEvent('nexfep-load-done'));
|
|
247
|
-
`;
|
|
248
|
-
await this.__injectCode(windowObj, INJECT_CODE);
|
|
249
|
-
}
|
|
250
|
-
async __createNewWindowObj() {
|
|
251
|
-
const window = this.app.createBrowserWindow({ title: "Nexfep" });
|
|
252
|
-
window.hide();
|
|
253
|
-
this.freeWindowCount++;
|
|
254
|
-
const webview = window.createWebview();
|
|
255
|
-
const windowObj = new Window(window, webview, ++this.windowCount, this);
|
|
256
|
-
this.windows.push(windowObj);
|
|
257
|
-
webview.onIpcMessage((data) => {
|
|
258
|
-
const dataText = data.body.toString();
|
|
259
|
-
const dataObj = JSON.parse(dataText);
|
|
260
|
-
if (dataObj.type == 'NexfepBeforeUnload') {
|
|
261
|
-
webview.evaluateScript(`if(window?.isNexfepLoadDone){
|
|
262
|
-
const MessageBody = { type: 'NexfepBeforeUnload' }
|
|
263
|
-
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
264
|
-
}else{
|
|
265
|
-
const MessageBody = { type: 'NexfepLoadFalse' }
|
|
266
|
-
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
267
|
-
}`);
|
|
268
|
-
}
|
|
269
|
-
else if (dataObj.type == 'NexfepLoadFalse') {
|
|
270
|
-
this.__injectControlFunctions(windowObj);
|
|
271
|
-
}
|
|
272
|
-
else if (dataObj.type == 'NexfepCloseWindow') {
|
|
273
|
-
this.closeWindow(windowObj);
|
|
274
|
-
}
|
|
275
|
-
else if (dataObj.type == 'NexfepMinimizeWindow') {
|
|
276
|
-
window.setMinimized(true);
|
|
277
|
-
}
|
|
278
|
-
else if (dataObj.type == 'NexfepUnMinimizeWindow') {
|
|
279
|
-
window.setMinimized(false);
|
|
280
|
-
}
|
|
281
|
-
else if (dataObj.type == 'NexfepMaximizeWindow') {
|
|
282
|
-
window.setMaximized(true);
|
|
283
|
-
}
|
|
284
|
-
else if (dataObj.type == 'NexfepUnMaximizeWindow') {
|
|
285
|
-
window.setMaximized(false);
|
|
286
|
-
}
|
|
287
|
-
else if (dataObj.type == 'NexfepSetTitle') {
|
|
288
|
-
window.setTitle(dataObj.title);
|
|
289
|
-
}
|
|
290
|
-
else if (dataObj.type == 'NexfepOpenDevTools') {
|
|
291
|
-
webview.openDevtools();
|
|
292
|
-
}
|
|
293
|
-
else if (dataObj.type == 'NexfepCloseDevTools') {
|
|
294
|
-
webview.closeDevtools();
|
|
295
|
-
}
|
|
296
|
-
else if (dataObj.type == 'CustomMessage') {
|
|
297
|
-
this.onCustomMessage(windowObj, dataObj.data);
|
|
298
|
-
}
|
|
299
|
-
else if (dataObj.type == 'NexfepInvoke') {
|
|
300
|
-
const handlers = this.handlers.get(dataObj.event) || [];
|
|
301
|
-
handlers.forEach(async (handler) => {
|
|
302
|
-
const result = await handler(dataObj.data);
|
|
303
|
-
if (result) {
|
|
304
|
-
webview.evaluateScript(`
|
|
305
|
-
window.dispatchEvent(new CustomEvent('nexfep-invoke-result-${dataObj.eventId[0]}-${dataObj.eventId[1]}', { detail: ${JSON.stringify(result)} }));
|
|
306
|
-
`);
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
webview.evaluateScript(`
|
|
310
|
-
window.dispatchEvent(new CustomEvent('nexfep-invoke-result-${dataObj.eventId[0]}-${dataObj.eventId[1]}', { detail: undefined }));
|
|
311
|
-
`);
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
else if (dataObj.type == 'NexfepSetGlobal') {
|
|
316
|
-
this.global.set(dataObj.name, dataObj.value);
|
|
317
|
-
}
|
|
318
|
-
else if (dataObj.type == 'NexfepGetGlobal') {
|
|
319
|
-
const value = this.global.get(dataObj.name);
|
|
320
|
-
webview.evaluateScript(`
|
|
321
|
-
window.dispatchEvent(new CustomEvent('nexfep-get-global-result-${dataObj.eventId[0]}-${dataObj.eventId[1]}', { detail: ${JSON.stringify(value)} }));
|
|
322
|
-
`);
|
|
323
|
-
}
|
|
324
|
-
else if (dataObj.type == 'NexfepBroadcast') {
|
|
325
|
-
this.windows.forEach(async (w) => {
|
|
326
|
-
if (w != windowObj && w.isOpen) {
|
|
327
|
-
w.webview.evaluateScript(`
|
|
328
|
-
window.dispatchEvent(new CustomEvent('${dataObj.name}', { detail: ${JSON.stringify(dataObj.data)} }));
|
|
329
|
-
`);
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
else if (dataObj.type == 'NexfepTell') {
|
|
334
|
-
this.windows.forEach(async (w) => {
|
|
335
|
-
if (w.id == dataObj.to) {
|
|
336
|
-
w.webview.evaluateScript(`
|
|
337
|
-
window.dispatchEvent(new CustomEvent('${dataObj.message}', { detail: ${JSON.stringify(dataObj.data)} }));
|
|
338
|
-
`);
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
return windowObj;
|
|
344
|
-
}
|
|
345
|
-
async createWindow(isShow = true, isDecorated = true) {
|
|
346
|
-
const window = this.windows.find(w => w.isOpen === false) || await this.__createNewWindowObj();
|
|
347
|
-
this.freeWindowCount--;
|
|
348
|
-
if (window) {
|
|
349
|
-
window.isOpen = true;
|
|
350
|
-
window.isShow = isShow;
|
|
351
|
-
window.setDecorated(isDecorated);
|
|
352
|
-
if (isShow) {
|
|
353
|
-
window.show();
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
if (this.freeWindowCount == 0) {
|
|
357
|
-
await this.__createNewWindowObj();
|
|
358
|
-
}
|
|
359
|
-
return window;
|
|
360
|
-
}
|
|
361
|
-
async closeWindow(window) {
|
|
362
|
-
if (window.isOpen) {
|
|
363
|
-
window.isOpen = false;
|
|
364
|
-
window.isShow = false;
|
|
365
|
-
window.setDecorated(true);
|
|
366
|
-
window.hide();
|
|
367
|
-
await window.loadHTML("<!DOCTYPE html><html><head></head><body></body></html>");
|
|
368
|
-
this.freeWindowCount++;
|
|
369
|
-
}
|
|
370
|
-
else {
|
|
371
|
-
throw new Error("Window is not open");
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
async closePool() {
|
|
375
|
-
this.app.exit();
|
|
376
|
-
}
|
|
377
|
-
async handle(event, callback) {
|
|
378
|
-
this.handlers.set(event, [...this.handlers.get(event) || [], callback]);
|
|
379
|
-
}
|
|
380
|
-
async unhandle(event, callback) {
|
|
381
|
-
this.handlers.set(event, (this.handlers.get(event) || []).filter(c => c !== callback));
|
|
382
|
-
}
|
|
383
|
-
mainloop() {
|
|
384
|
-
this.app.run();
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
export { WindowPool, Window };
|
|
1
|
+
export * from './src/WindowManager.js';
|
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"webview",
|
|
15
15
|
"typescript"
|
|
16
16
|
],
|
|
17
|
-
"version": "0.1.
|
|
17
|
+
"version": "0.1.7",
|
|
18
18
|
"type": "module",
|
|
19
19
|
"main": "./index.js",
|
|
20
20
|
"scripts": {
|
|
@@ -31,7 +31,9 @@
|
|
|
31
31
|
"README.md",
|
|
32
32
|
"README-CN.md",
|
|
33
33
|
"LICENSE",
|
|
34
|
-
"package.json"
|
|
34
|
+
"package.json",
|
|
35
|
+
"src/WindowManager.js",
|
|
36
|
+
"src/WindowManager.d.ts"
|
|
35
37
|
],
|
|
36
38
|
"dependencies": {
|
|
37
39
|
"@webviewjs/webview": "0.1.4"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { BrowserWindow, Webview } from "@webviewjs/webview";
|
|
2
|
+
declare class Window {
|
|
3
|
+
window: BrowserWindow;
|
|
4
|
+
webview: Webview;
|
|
5
|
+
isOpen: boolean;
|
|
6
|
+
isShow: boolean;
|
|
7
|
+
isDecorated: boolean;
|
|
8
|
+
id: number;
|
|
9
|
+
private myPool;
|
|
10
|
+
constructor(window: BrowserWindow, webview: Webview, id: number, myPool: WindowPool);
|
|
11
|
+
setDecorated(isDecorated: boolean): void;
|
|
12
|
+
setTitle(title: string): void;
|
|
13
|
+
resizable(resizable: boolean): void;
|
|
14
|
+
maximize(): void;
|
|
15
|
+
minimize(): void;
|
|
16
|
+
unMaximize(): void;
|
|
17
|
+
unMinimize(): void;
|
|
18
|
+
openDevTools(): void;
|
|
19
|
+
closeDevTools(): void;
|
|
20
|
+
hide(): void;
|
|
21
|
+
show(): void;
|
|
22
|
+
close(): void;
|
|
23
|
+
loadURL(url: string): Promise<void>;
|
|
24
|
+
loadHTML(html: string): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
declare class WindowPool {
|
|
27
|
+
onCustomMessage: (window: Window, data: string) => void;
|
|
28
|
+
private app;
|
|
29
|
+
private windows;
|
|
30
|
+
private handlers;
|
|
31
|
+
private injectCount;
|
|
32
|
+
private windowCount;
|
|
33
|
+
private freeWindowCount;
|
|
34
|
+
global: Map<string, any>;
|
|
35
|
+
constructor(WindowsWebview2UserDataFolder?: string);
|
|
36
|
+
__injectCode(window: Window, code: string): Promise<void>;
|
|
37
|
+
__injectControlFunctions(windowObj: Window): Promise<void>;
|
|
38
|
+
__createNewWindowObj(): Promise<Window>;
|
|
39
|
+
createWindow(isShow?: boolean, isDecorated?: boolean): Promise<Window>;
|
|
40
|
+
closeWindow(window: Window): Promise<void>;
|
|
41
|
+
closePool(): Promise<void>;
|
|
42
|
+
handle(event: string, callback: (data: any) => any): Promise<void>;
|
|
43
|
+
unhandle(event: string, callback: (data: any) => any): Promise<void>;
|
|
44
|
+
mainloop(): void;
|
|
45
|
+
}
|
|
46
|
+
export { WindowPool, Window };
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { Application } from "@webviewjs/webview";
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
class Window {
|
|
6
|
+
window;
|
|
7
|
+
webview;
|
|
8
|
+
isOpen;
|
|
9
|
+
isShow;
|
|
10
|
+
isDecorated;
|
|
11
|
+
id;
|
|
12
|
+
myPool;
|
|
13
|
+
constructor(window, webview, id, myPool) {
|
|
14
|
+
this.myPool = myPool;
|
|
15
|
+
this.window = window;
|
|
16
|
+
this.webview = webview;
|
|
17
|
+
this.isOpen = false;
|
|
18
|
+
this.isShow = false;
|
|
19
|
+
this.isDecorated = false;
|
|
20
|
+
this.id = id;
|
|
21
|
+
}
|
|
22
|
+
setDecorated(isDecorated) {
|
|
23
|
+
this.window.setDecorations(isDecorated);
|
|
24
|
+
this.isDecorated = isDecorated;
|
|
25
|
+
}
|
|
26
|
+
setTitle(title) {
|
|
27
|
+
this.window.setTitle(title);
|
|
28
|
+
}
|
|
29
|
+
resizable(resizable) {
|
|
30
|
+
this.window.setResizable(resizable);
|
|
31
|
+
}
|
|
32
|
+
maximize() {
|
|
33
|
+
this.window.setMaximized(true);
|
|
34
|
+
}
|
|
35
|
+
minimize() {
|
|
36
|
+
this.window.setMinimized(true);
|
|
37
|
+
}
|
|
38
|
+
unMaximize() {
|
|
39
|
+
this.window.setMaximized(false);
|
|
40
|
+
}
|
|
41
|
+
unMinimize() {
|
|
42
|
+
this.window.setMinimized(false);
|
|
43
|
+
}
|
|
44
|
+
openDevTools() {
|
|
45
|
+
this.webview.openDevtools();
|
|
46
|
+
}
|
|
47
|
+
closeDevTools() {
|
|
48
|
+
this.webview.closeDevtools();
|
|
49
|
+
}
|
|
50
|
+
hide() {
|
|
51
|
+
this.window.hide();
|
|
52
|
+
this.isShow = false;
|
|
53
|
+
}
|
|
54
|
+
show() {
|
|
55
|
+
this.window.show();
|
|
56
|
+
this.isShow = true;
|
|
57
|
+
}
|
|
58
|
+
close() {
|
|
59
|
+
this.myPool.closeWindow(this);
|
|
60
|
+
}
|
|
61
|
+
async loadURL(url) {
|
|
62
|
+
await this.webview.loadUrl(url);
|
|
63
|
+
await this.myPool.__injectControlFunctions(this);
|
|
64
|
+
}
|
|
65
|
+
async loadHTML(html) {
|
|
66
|
+
await this.webview.loadHtml(html);
|
|
67
|
+
await this.myPool.__injectControlFunctions(this);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
class WindowPool {
|
|
71
|
+
onCustomMessage;
|
|
72
|
+
app;
|
|
73
|
+
windows;
|
|
74
|
+
handlers;
|
|
75
|
+
injectCount;
|
|
76
|
+
windowCount;
|
|
77
|
+
freeWindowCount;
|
|
78
|
+
global;
|
|
79
|
+
constructor(WindowsWebview2UserDataFolder = path.join(process.env.LOCALAPPDATA || os.homedir(), 'NexfepDevelopment.webview2-data')) {
|
|
80
|
+
if (os.platform() === 'win32') {
|
|
81
|
+
// 设置 WebView2 用户数据目录(仅 Windows 需要此配置)
|
|
82
|
+
const userDataDir = WindowsWebview2UserDataFolder;
|
|
83
|
+
if (!fs.existsSync(userDataDir)) {
|
|
84
|
+
fs.mkdirSync(userDataDir, { recursive: true });
|
|
85
|
+
}
|
|
86
|
+
process.env.WEBVIEW2_USER_DATA_FOLDER = userDataDir;
|
|
87
|
+
}
|
|
88
|
+
this.onCustomMessage = (window, data) => {
|
|
89
|
+
console.log('Get Custom Message:', data, "from window", window.id);
|
|
90
|
+
};
|
|
91
|
+
this.handlers = new Map();
|
|
92
|
+
this.app = new Application();
|
|
93
|
+
this.injectCount = 0;
|
|
94
|
+
this.windows = [];
|
|
95
|
+
this.windowCount = 0;
|
|
96
|
+
this.freeWindowCount = 0;
|
|
97
|
+
this.global = new Map();
|
|
98
|
+
}
|
|
99
|
+
async __injectCode(window, code) {
|
|
100
|
+
await window.webview.evaluateScript(code);
|
|
101
|
+
}
|
|
102
|
+
async __injectControlFunctions(windowObj) {
|
|
103
|
+
const INJECT_CSS = `
|
|
104
|
+
[nexfep-area-drag],
|
|
105
|
+
[nexfep-area-drag] * {
|
|
106
|
+
-webkit-app-region: drag;
|
|
107
|
+
app-region: drag;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
[nexfep-element-drag] {
|
|
111
|
+
-webkit-app-region: drag;
|
|
112
|
+
app-region: drag;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
[nexfep-no-drag],
|
|
116
|
+
[nexfep-no-drag] * {
|
|
117
|
+
-webkit-app-region: no-drag !important;
|
|
118
|
+
app-region: no-drag !important;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
[nexfep-auto-drag],
|
|
122
|
+
[nexfep-auto-drag] * {
|
|
123
|
+
-webkit-app-region: drag;
|
|
124
|
+
app-region: drag;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
[nexfep-auto-drag] button,
|
|
128
|
+
[nexfep-auto-drag] input,
|
|
129
|
+
[nexfep-auto-drag] select,
|
|
130
|
+
[nexfep-auto-drag] textarea,
|
|
131
|
+
[nexfep-auto-drag] a {
|
|
132
|
+
-webkit-app-region: no-drag;
|
|
133
|
+
app-region: no-drag;
|
|
134
|
+
}
|
|
135
|
+
`;
|
|
136
|
+
const INJECT_CODE = `
|
|
137
|
+
// 事件监听
|
|
138
|
+
window.addEventListener("beforeunload", () => {
|
|
139
|
+
const MessageBody = { type: 'NexfepBeforeUnload' }
|
|
140
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// 窗口控制函数
|
|
144
|
+
window.close = () => {
|
|
145
|
+
const MessageBody = { type: 'NexfepCloseWindow' }
|
|
146
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
window.minimize = () => {
|
|
150
|
+
const MessageBody = { type: 'NexfepMinimizeWindow' }
|
|
151
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
window.unminimize = () => {
|
|
155
|
+
const MessageBody = { type: 'NexfepUnMinimizeWindow' }
|
|
156
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
window.maximize = () => {
|
|
160
|
+
const MessageBody = { type: 'NexfepMaximizeWindow' }
|
|
161
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
window.unmaximize = () => {
|
|
165
|
+
const MessageBody = { type: 'NexfepUnMaximizeWindow' }
|
|
166
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
167
|
+
};
|
|
168
|
+
window.setTitle = (title) => {
|
|
169
|
+
const MessageBody = { type: 'NexfepSetTitle', title: title }
|
|
170
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
window.openDevTools = () => {
|
|
174
|
+
const MessageBody = { type: 'NexfepOpenDevTools' }
|
|
175
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
window.closeDevTools = () => {
|
|
179
|
+
const MessageBody = { type: 'NexfepCloseDevTools' }
|
|
180
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
window.postMessage = (data) => {
|
|
184
|
+
const MessageBody = { type: 'CustomMessage', data: data }
|
|
185
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
window.eventCount = 0;
|
|
189
|
+
|
|
190
|
+
window.invoke = async (event, data = null) => {
|
|
191
|
+
const MessageBody = { type: 'NexfepInvoke', eventId: [${this.injectCount}, ++window.eventCount], event: event, data: data }
|
|
192
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
193
|
+
return new Promise((resolve, reject) => {
|
|
194
|
+
window.addEventListener("nexfep-invoke-result-"+${this.injectCount}+"-"+window.eventCount, (event) => {
|
|
195
|
+
window.removeEventListener("nexfep-invoke-result-"+${this.injectCount}+"-"+window.eventCount, event);
|
|
196
|
+
if(event.detail){
|
|
197
|
+
resolve(event.detail);
|
|
198
|
+
}else{
|
|
199
|
+
resolve();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
window.setGlobal = (name, value) => {
|
|
206
|
+
const MessageBody = { type: 'NexfepSetGlobal', name: name, value: value }
|
|
207
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
window.getGlobal = (name) => {
|
|
211
|
+
const MessageBody = { type: 'NexfepGetGlobal', eventId: [${this.injectCount}, ++window.eventCount], name: name }
|
|
212
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
213
|
+
return new Promise((resolve, reject) => {
|
|
214
|
+
window.addEventListener("nexfep-get-global-result-"+${this.injectCount}+"-"+window.eventCount, (event) => {
|
|
215
|
+
window.removeEventListener("nexfep-get-global-result-"+${this.injectCount}+"-"+window.eventCount, event);
|
|
216
|
+
if(event.detail){
|
|
217
|
+
resolve(event.detail);
|
|
218
|
+
}else{
|
|
219
|
+
resolve();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
window.broadcast = (name, data = null) => {
|
|
226
|
+
const MessageBody = { type: 'NexfepBroadcast', name: name, data: data }
|
|
227
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
window.id = ${windowObj.id};
|
|
231
|
+
|
|
232
|
+
window.tell = (to, message, data = null) => {
|
|
233
|
+
const MessageBody = { type: 'NexfepTell', to: to, message: message, data: data }
|
|
234
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 注入 CSS 样式
|
|
238
|
+
const style = document.createElement('style');
|
|
239
|
+
style.textContent = \`${INJECT_CSS}\`;
|
|
240
|
+
document.head.appendChild(style);
|
|
241
|
+
|
|
242
|
+
// 标记加载完成
|
|
243
|
+
window.isNexfepLoadDone = true;
|
|
244
|
+
|
|
245
|
+
// 触发加载完成事件
|
|
246
|
+
window.dispatchEvent(new CustomEvent('nexfep-load-done'));
|
|
247
|
+
`;
|
|
248
|
+
await this.__injectCode(windowObj, INJECT_CODE);
|
|
249
|
+
}
|
|
250
|
+
async __createNewWindowObj() {
|
|
251
|
+
const window = this.app.createBrowserWindow({ title: "Nexfep" });
|
|
252
|
+
window.hide();
|
|
253
|
+
this.freeWindowCount++;
|
|
254
|
+
const webview = window.createWebview();
|
|
255
|
+
const windowObj = new Window(window, webview, ++this.windowCount, this);
|
|
256
|
+
this.windows.push(windowObj);
|
|
257
|
+
webview.onIpcMessage((data) => {
|
|
258
|
+
const dataText = data.body.toString();
|
|
259
|
+
const dataObj = JSON.parse(dataText);
|
|
260
|
+
if (dataObj.type == 'NexfepBeforeUnload') {
|
|
261
|
+
webview.evaluateScript(`if(window?.isNexfepLoadDone){
|
|
262
|
+
const MessageBody = { type: 'NexfepBeforeUnload' }
|
|
263
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
264
|
+
}else{
|
|
265
|
+
const MessageBody = { type: 'NexfepLoadFalse' }
|
|
266
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
267
|
+
}`);
|
|
268
|
+
}
|
|
269
|
+
else if (dataObj.type == 'NexfepLoadFalse') {
|
|
270
|
+
this.__injectControlFunctions(windowObj);
|
|
271
|
+
}
|
|
272
|
+
else if (dataObj.type == 'NexfepCloseWindow') {
|
|
273
|
+
this.closeWindow(windowObj);
|
|
274
|
+
}
|
|
275
|
+
else if (dataObj.type == 'NexfepMinimizeWindow') {
|
|
276
|
+
window.setMinimized(true);
|
|
277
|
+
}
|
|
278
|
+
else if (dataObj.type == 'NexfepUnMinimizeWindow') {
|
|
279
|
+
window.setMinimized(false);
|
|
280
|
+
}
|
|
281
|
+
else if (dataObj.type == 'NexfepMaximizeWindow') {
|
|
282
|
+
window.setMaximized(true);
|
|
283
|
+
}
|
|
284
|
+
else if (dataObj.type == 'NexfepUnMaximizeWindow') {
|
|
285
|
+
window.setMaximized(false);
|
|
286
|
+
}
|
|
287
|
+
else if (dataObj.type == 'NexfepSetTitle') {
|
|
288
|
+
window.setTitle(dataObj.title);
|
|
289
|
+
}
|
|
290
|
+
else if (dataObj.type == 'NexfepOpenDevTools') {
|
|
291
|
+
webview.openDevtools();
|
|
292
|
+
}
|
|
293
|
+
else if (dataObj.type == 'NexfepCloseDevTools') {
|
|
294
|
+
webview.closeDevtools();
|
|
295
|
+
}
|
|
296
|
+
else if (dataObj.type == 'CustomMessage') {
|
|
297
|
+
this.onCustomMessage(windowObj, dataObj.data);
|
|
298
|
+
}
|
|
299
|
+
else if (dataObj.type == 'NexfepInvoke') {
|
|
300
|
+
const handlers = this.handlers.get(dataObj.event) || [];
|
|
301
|
+
handlers.forEach(async (handler) => {
|
|
302
|
+
const result = await handler(dataObj.data);
|
|
303
|
+
if (result) {
|
|
304
|
+
webview.evaluateScript(`
|
|
305
|
+
window.dispatchEvent(new CustomEvent('nexfep-invoke-result-${dataObj.eventId[0]}-${dataObj.eventId[1]}', { detail: ${JSON.stringify(result)} }));
|
|
306
|
+
`);
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
webview.evaluateScript(`
|
|
310
|
+
window.dispatchEvent(new CustomEvent('nexfep-invoke-result-${dataObj.eventId[0]}-${dataObj.eventId[1]}', { detail: undefined }));
|
|
311
|
+
`);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
else if (dataObj.type == 'NexfepSetGlobal') {
|
|
316
|
+
this.global.set(dataObj.name, dataObj.value);
|
|
317
|
+
}
|
|
318
|
+
else if (dataObj.type == 'NexfepGetGlobal') {
|
|
319
|
+
const value = this.global.get(dataObj.name);
|
|
320
|
+
webview.evaluateScript(`
|
|
321
|
+
window.dispatchEvent(new CustomEvent('nexfep-get-global-result-${dataObj.eventId[0]}-${dataObj.eventId[1]}', { detail: ${JSON.stringify(value)} }));
|
|
322
|
+
`);
|
|
323
|
+
}
|
|
324
|
+
else if (dataObj.type == 'NexfepBroadcast') {
|
|
325
|
+
this.windows.forEach(async (w) => {
|
|
326
|
+
if (w != windowObj && w.isOpen) {
|
|
327
|
+
w.webview.evaluateScript(`
|
|
328
|
+
window.dispatchEvent(new CustomEvent('${dataObj.name}', { detail: ${JSON.stringify(dataObj.data)} }));
|
|
329
|
+
`);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
else if (dataObj.type == 'NexfepTell') {
|
|
334
|
+
this.windows.forEach(async (w) => {
|
|
335
|
+
if (w.id == dataObj.to) {
|
|
336
|
+
w.webview.evaluateScript(`
|
|
337
|
+
window.dispatchEvent(new CustomEvent('${dataObj.message}', { detail: ${JSON.stringify(dataObj.data)} }));
|
|
338
|
+
`);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
return windowObj;
|
|
344
|
+
}
|
|
345
|
+
async createWindow(isShow = true, isDecorated = true) {
|
|
346
|
+
const window = this.windows.find(w => w.isOpen === false) || await this.__createNewWindowObj();
|
|
347
|
+
this.freeWindowCount--;
|
|
348
|
+
if (window) {
|
|
349
|
+
window.isOpen = true;
|
|
350
|
+
window.isShow = isShow;
|
|
351
|
+
window.setDecorated(isDecorated);
|
|
352
|
+
if (isShow) {
|
|
353
|
+
window.show();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (this.freeWindowCount == 0) {
|
|
357
|
+
await this.__createNewWindowObj();
|
|
358
|
+
}
|
|
359
|
+
return window;
|
|
360
|
+
}
|
|
361
|
+
async closeWindow(window) {
|
|
362
|
+
if (window.isOpen) {
|
|
363
|
+
window.isOpen = false;
|
|
364
|
+
window.isShow = false;
|
|
365
|
+
window.setDecorated(true);
|
|
366
|
+
window.hide();
|
|
367
|
+
await window.loadHTML("<!DOCTYPE html><html><head></head><body></body></html>");
|
|
368
|
+
this.freeWindowCount++;
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
throw new Error("Window is not open");
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
async closePool() {
|
|
375
|
+
this.app.exit();
|
|
376
|
+
}
|
|
377
|
+
async handle(event, callback) {
|
|
378
|
+
this.handlers.set(event, [...this.handlers.get(event) || [], callback]);
|
|
379
|
+
}
|
|
380
|
+
async unhandle(event, callback) {
|
|
381
|
+
this.handlers.set(event, (this.handlers.get(event) || []).filter(c => c !== callback));
|
|
382
|
+
}
|
|
383
|
+
mainloop() {
|
|
384
|
+
this.app.run();
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
export { WindowPool, Window };
|