nexfep 0.0.1
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/LICENSE +21 -0
- package/README.md +238 -0
- package/index.d.ts +41 -0
- package/index.js +290 -0
- package/package.json +23 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 zhuxiaojt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Nexfep
|
|
2
|
+
|
|
3
|
+
基于 WebView 的桌面应用框架
|
|
4
|
+
|
|
5
|
+
## 项目状态
|
|
6
|
+
|
|
7
|
+
**🚧 早期阶段**
|
|
8
|
+
|
|
9
|
+
本项目目前处于早期开发阶段,仍缺失大量桌面应用开发所需的核心能力。框架正在持续迭代中。
|
|
10
|
+
|
|
11
|
+
## 简介
|
|
12
|
+
|
|
13
|
+
Nexfep 是一个基于 [@webviewjs/webview](https://github.com/webview/webview) 构建的桌面应用框架,使用 TypeScript 编写。它旨在为开发者提供一套简洁、高效的工具链,用于构建跨平台桌面应用。
|
|
14
|
+
|
|
15
|
+
框架采用窗口池管理机制,支持多窗口应用场景,如代码编辑器、聊天工具、仪表盘等。
|
|
16
|
+
|
|
17
|
+
## 特性
|
|
18
|
+
|
|
19
|
+
- **窗口池管理** — 内置窗口池机制,自动复用和回收窗口资源,避免频繁创建销毁的开销
|
|
20
|
+
- **IPC 通信** — 支持主进程与 WebView 之间的双向消息通信,通过注入的函数进行调用
|
|
21
|
+
- **窗口控制** — 提供最大化、最小化、关闭、标题设置、开发者工具等完整窗口操作 API
|
|
22
|
+
- **拖拽区域** — 内置 HTML 属性支持,方便定义窗口拖拽区域(`nexfep-area-drag` 等)
|
|
23
|
+
- **TypeScript 支持** — 完整的类型定义,开发体验优秀
|
|
24
|
+
|
|
25
|
+
## 安装
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm add nexfep
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 快速开始
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { WindowPool } from 'nexfep';
|
|
35
|
+
|
|
36
|
+
const pool = new WindowPool();
|
|
37
|
+
|
|
38
|
+
const window = await pool.createWindow(true, false);
|
|
39
|
+
|
|
40
|
+
await window.loadHTML('<h1 nexfep-area-drag>Hello Nexfep!</h1>');
|
|
41
|
+
|
|
42
|
+
pool.mainloop();
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 使用指南
|
|
46
|
+
|
|
47
|
+
### 窗口池
|
|
48
|
+
|
|
49
|
+
`WindowPool` 是框架的核心管理类,负责窗口的创建和回收。
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
const pool = new WindowPool();
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**构造函数参数**
|
|
56
|
+
|
|
57
|
+
- `WindowsWebview2UserDataFolder`(可选)— WebView2 用户数据目录,默认为 `%LOCALAPPDATA%\NexfepDevelopment.webview2-data`
|
|
58
|
+
|
|
59
|
+
### 窗口创建
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
const win = await pool.createWindow(true, false);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**参数说明**
|
|
66
|
+
|
|
67
|
+
- `isShow`(布尔值,默认 `true`)— 是否立即显示窗口
|
|
68
|
+
- `isDecorated`(布尔值,默认 `true`)— 是否使用系统窗口装饰。设为 `false` 时,窗口无边框,需要自定义标题栏
|
|
69
|
+
|
|
70
|
+
### 窗口操作
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
window.show();
|
|
74
|
+
window.hide();
|
|
75
|
+
window.maximize();
|
|
76
|
+
window.minimize();
|
|
77
|
+
window.close();
|
|
78
|
+
window.setTitle('新标题');
|
|
79
|
+
window.openDevTools();
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### IPC 通信
|
|
83
|
+
|
|
84
|
+
框架在 WebView 页面加载完成后会自动注入一系列控制函数,建议使用这些注入的函数进行 IPC 通信,而非直接使用 `@webviewjs/webview` 原生包装的 IPC。
|
|
85
|
+
|
|
86
|
+
#### 发送自定义消息
|
|
87
|
+
|
|
88
|
+
在页面中通过 `window.postMessage` 发送消息:
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
window.postMessage({ hello: 'world' });
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**参数说明**
|
|
95
|
+
|
|
96
|
+
- `data` — 任意类型的可序列化数据,会被序列化为 JSON 字符串后发送
|
|
97
|
+
|
|
98
|
+
#### 监听消息
|
|
99
|
+
|
|
100
|
+
在主进程中通过 `onCustomMessage` 回调接收消息:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
pool.onCustomMessage = (window, data) => {
|
|
104
|
+
console.log(`来自窗口 ${window.id} 的消息:`, data);
|
|
105
|
+
};
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**回调参数**
|
|
109
|
+
|
|
110
|
+
- `window` — 发送消息的窗口对象
|
|
111
|
+
- `data` — 消息内容,为对象类型(JSON 序列化后会自动通过 `JSON.parse` 转换为对象)
|
|
112
|
+
|
|
113
|
+
#### 窗口控制函数
|
|
114
|
+
|
|
115
|
+
页面中可直接调用以下注入函数进行窗口控制:
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
window.close(); // 关闭窗口
|
|
119
|
+
window.minimize(); // 最小化窗口
|
|
120
|
+
window.unminimize(); // 还原最小化的窗口
|
|
121
|
+
window.maximize(); // 最大化窗口
|
|
122
|
+
window.unmaximize(); // 还原最大化的窗口
|
|
123
|
+
window.setTitle('标题'); // 设置窗口标题
|
|
124
|
+
window.openDevTools(); // 打开开发者工具
|
|
125
|
+
window.closeDevTools(); // 关闭开发者工具
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 拖拽区域
|
|
129
|
+
|
|
130
|
+
通过 HTML 属性即可定义窗口拖拽区域,无需编写额外 JavaScript 代码。这些属性会自动应用 `-webkit-app-region` 和 `app-region` CSS 属性。
|
|
131
|
+
|
|
132
|
+
#### `nexfep-area-drag`
|
|
133
|
+
|
|
134
|
+
使整个区域及其所有子元素均可拖拽。适用于自定义标题栏等需要整块区域可拖拽的场景。
|
|
135
|
+
|
|
136
|
+
```html
|
|
137
|
+
<div nexfep-area-drag>
|
|
138
|
+
<h1>标题栏</h1>
|
|
139
|
+
<span>副标题</span>
|
|
140
|
+
</div>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### `nexfep-element-drag`
|
|
144
|
+
|
|
145
|
+
仅使指定元素本身可拖拽,子元素不受影响。适用于需要精确控制拖拽区域的场景。
|
|
146
|
+
|
|
147
|
+
```html
|
|
148
|
+
<div>
|
|
149
|
+
<div nexfep-element-drag>拖拽手柄</div>
|
|
150
|
+
<p>这部分不可拖拽</p>
|
|
151
|
+
</div>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### `nexfep-no-drag`
|
|
155
|
+
|
|
156
|
+
使指定区域及其所有子元素不可拖拽,优先级最高,可覆盖父元素的拖拽属性。适用于按钮、输入框等交互元素。
|
|
157
|
+
|
|
158
|
+
```html
|
|
159
|
+
<div nexfep-area-drag>
|
|
160
|
+
<h1>标题栏</h1>
|
|
161
|
+
<button nexfep-no-drag>点击按钮</button>
|
|
162
|
+
</div>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### `nexfep-auto-drag`
|
|
166
|
+
|
|
167
|
+
自动判断拖拽区域:整个区域可拖拽,但常见交互元素(`button`、`input`、`select`、`textarea`、`a`)自动设为不可拖拽。适用于包含多种交互元素的复杂区域。
|
|
168
|
+
|
|
169
|
+
```html
|
|
170
|
+
<div nexfep-auto-drag>
|
|
171
|
+
<h1>标题栏</h1>
|
|
172
|
+
<button>自动不可拖拽</button>
|
|
173
|
+
<input placeholder="自动不可拖拽" />
|
|
174
|
+
<a href="#">自动不可拖拽</a>
|
|
175
|
+
</div>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 加载完成事件
|
|
179
|
+
|
|
180
|
+
WebView 窗口加载完成后会触发 `nexfep-load-done` 事件:
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
window.addEventListener('nexfep-load-done', () => {
|
|
184
|
+
console.log('Nexfep 窗口加载完成');
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
也可通过 `window.isNexfepLoadDone` 属性判断:
|
|
189
|
+
|
|
190
|
+
```javascript
|
|
191
|
+
if (window.isNexfepLoadDone) {
|
|
192
|
+
// 窗口已就绪
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## API
|
|
197
|
+
|
|
198
|
+
### WindowPool
|
|
199
|
+
|
|
200
|
+
| 方法/属性 | 参数 | 返回值 | 说明 |
|
|
201
|
+
| ------------------------------------- | ----------------------------------------------------------- | ---------------- | -------------------------- |
|
|
202
|
+
| `constructor(userDataFolder?)` | `userDataFolder`: string(可选) | WindowPool | 创建窗口池,可选指定 WebView2 用户数据目录 |
|
|
203
|
+
| `createWindow(isShow?, isDecorated?)` | `isShow`: boolean(默认 true), `isDecorated`: boolean(默认 true) | Promise\<Window> | 创建并获取一个窗口 |
|
|
204
|
+
| `closeWindow(window)` | `window`: Window | Promise\<void> | 关闭指定窗口并回收至池中 |
|
|
205
|
+
| `closePool()` | 无 | Promise\<void> | 关闭窗口池,退出应用 |
|
|
206
|
+
| `mainloop()` | 无 | void | 启动应用主循环,阻塞直到应用退出 |
|
|
207
|
+
| `onCustomMessage` | `(window: Window, data: string) => void` | 无 | 自定义消息回调函数,当收到页面发来的自定义消息时触发 |
|
|
208
|
+
|
|
209
|
+
### Window
|
|
210
|
+
|
|
211
|
+
| 方法/属性 | 参数 | 返回值 | 说明 |
|
|
212
|
+
| --------------------------- | --------------------------------- | -------------- | ------------- |
|
|
213
|
+
| `loadURL(url)` | `url`: string — 要加载的网页地址 | Promise\<void> | 加载指定 URL |
|
|
214
|
+
| `loadHTML(html)` | `html`: string — HTML 字符串 | Promise\<void> | 加载指定 HTML 内容 |
|
|
215
|
+
| `show()` | 无 | void | 显示窗口 |
|
|
216
|
+
| `hide()` | 无 | void | 隐藏窗口 |
|
|
217
|
+
| `maximize()` | 无 | void | 最大化窗口 |
|
|
218
|
+
| `unMaximize()` | 无 | void | 还原窗口(取消最大化) |
|
|
219
|
+
| `minimize()` | 无 | void | 最小化窗口 |
|
|
220
|
+
| `unMinimize()` | 无 | void | 还原窗口(取消最小化) |
|
|
221
|
+
| `close()` | 无 | void | 关闭窗口并回收至池中 |
|
|
222
|
+
| `setTitle(title)` | `title`: string — 窗口标题 | void | 设置窗口标题 |
|
|
223
|
+
| `setDecorated(isDecorated)` | `isDecorated`: boolean — 是否使用系统装饰 | void | 设置窗口是否带边框和标题栏 |
|
|
224
|
+
| `resizable(resizable)` | `resizable`: boolean — 是否可调整大小 | void | 设置窗口是否可调整大小 |
|
|
225
|
+
| `openDevTools()` | 无 | void | 打开开发者工具 |
|
|
226
|
+
| `closeDevTools()` | 无 | void | 关闭开发者工具 |
|
|
227
|
+
| `id` | 无 | number | 窗口唯一标识,自增编号 |
|
|
228
|
+
|
|
229
|
+
## 开发
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
pnpm install
|
|
233
|
+
pnpm run compile
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## 许可证
|
|
237
|
+
|
|
238
|
+
MIT License
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
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 windowCount;
|
|
31
|
+
private freeWindowCount;
|
|
32
|
+
constructor(WindowsWebview2UserDataFolder?: string);
|
|
33
|
+
__injectCode(window: Window, code: string): Promise<void>;
|
|
34
|
+
__injectControlFunctions(windowObj: Window): Promise<void>;
|
|
35
|
+
__createNewWindowObj(): Promise<Window>;
|
|
36
|
+
createWindow(isShow?: boolean, isDecorated?: boolean): Promise<Window>;
|
|
37
|
+
closeWindow(window: Window): Promise<void>;
|
|
38
|
+
closePool(): Promise<void>;
|
|
39
|
+
mainloop(): void;
|
|
40
|
+
}
|
|
41
|
+
export { WindowPool, Window };
|
package/index.js
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
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
|
+
console.log("Before loadHTML");
|
|
67
|
+
await this.webview.loadHtml(html);
|
|
68
|
+
await this.myPool.__injectControlFunctions(this);
|
|
69
|
+
console.log("After loadHTML");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
class WindowPool {
|
|
73
|
+
onCustomMessage;
|
|
74
|
+
app;
|
|
75
|
+
windows;
|
|
76
|
+
windowCount;
|
|
77
|
+
freeWindowCount;
|
|
78
|
+
constructor(WindowsWebview2UserDataFolder = path.join(process.env.LOCALAPPDATA || os.homedir(), 'NexfepDevelopment.webview2-data')) {
|
|
79
|
+
if (os.platform() === 'win32') {
|
|
80
|
+
// 设置 WebView2 用户数据目录(仅 Windows 需要此配置)
|
|
81
|
+
const userDataDir = WindowsWebview2UserDataFolder;
|
|
82
|
+
if (!fs.existsSync(userDataDir)) {
|
|
83
|
+
fs.mkdirSync(userDataDir, { recursive: true });
|
|
84
|
+
}
|
|
85
|
+
process.env.WEBVIEW2_USER_DATA_FOLDER = userDataDir;
|
|
86
|
+
console.log('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.app = new Application();
|
|
92
|
+
this.windows = [];
|
|
93
|
+
this.windowCount = 0;
|
|
94
|
+
this.freeWindowCount = 0;
|
|
95
|
+
}
|
|
96
|
+
async __injectCode(window, code) {
|
|
97
|
+
await window.webview.evaluateScript(code);
|
|
98
|
+
}
|
|
99
|
+
async __injectControlFunctions(windowObj) {
|
|
100
|
+
console.log("Create INJECT_CODE");
|
|
101
|
+
const INJECT_CSS = `
|
|
102
|
+
[nexfep-area-drag],
|
|
103
|
+
[nexfep-area-drag] * {
|
|
104
|
+
-webkit-app-region: drag;
|
|
105
|
+
app-region: drag;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
[nexfep-element-drag] {
|
|
109
|
+
-webkit-app-region: drag;
|
|
110
|
+
app-region: drag;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
[nexfep-no-drag],
|
|
114
|
+
[nexfep-no-drag] * {
|
|
115
|
+
-webkit-app-region: no-drag !important;
|
|
116
|
+
app-region: no-drag !important;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
[nexfep-auto-drag],
|
|
120
|
+
[nexfep-auto-drag] * {
|
|
121
|
+
-webkit-app-region: drag;
|
|
122
|
+
app-region: drag;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
[nexfep-auto-drag] button,
|
|
126
|
+
[nexfep-auto-drag] input,
|
|
127
|
+
[nexfep-auto-drag] select,
|
|
128
|
+
[nexfep-auto-drag] textarea,
|
|
129
|
+
[nexfep-auto-drag] a {
|
|
130
|
+
-webkit-app-region: no-drag;
|
|
131
|
+
app-region: no-drag;
|
|
132
|
+
}
|
|
133
|
+
`;
|
|
134
|
+
const INJECT_CODE = `
|
|
135
|
+
// 事件监听
|
|
136
|
+
window.addEventListener("beforeunload", () => {
|
|
137
|
+
const MessageBody = { type: 'NexfepBeforeUnload' }
|
|
138
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// 窗口控制函数
|
|
142
|
+
window.close = () => {
|
|
143
|
+
const MessageBody = { type: 'NexfepCloseWindow' }
|
|
144
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
window.minimize = () => {
|
|
148
|
+
const MessageBody = { type: 'NexfepMinimizeWindow' }
|
|
149
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
window.unminimize = () => {
|
|
153
|
+
const MessageBody = { type: 'NexfepUnMinimizeWindow' }
|
|
154
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
window.maximize = () => {
|
|
158
|
+
const MessageBody = { type: 'NexfepMaximizeWindow' }
|
|
159
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
window.unmaximize = () => {
|
|
163
|
+
const MessageBody = { type: 'NexfepUnMaximizeWindow' }
|
|
164
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
165
|
+
};
|
|
166
|
+
window.setTitle = (title) => {
|
|
167
|
+
const MessageBody = { type: 'NexfepSetTitle', title: title }
|
|
168
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
window.openDevTools = () => {
|
|
172
|
+
const MessageBody = { type: 'NexfepOpenDevTools' }
|
|
173
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
window.closeDevTools = () => {
|
|
177
|
+
const MessageBody = { type: 'NexfepCloseDevTools' }
|
|
178
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
window.postMessage = (data) => {
|
|
182
|
+
const MessageBody = { type: 'CustomMessage', data: data }
|
|
183
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// 注入 CSS 样式
|
|
187
|
+
const style = document.createElement('style');
|
|
188
|
+
style.textContent = \`${INJECT_CSS}\`;
|
|
189
|
+
document.head.appendChild(style);
|
|
190
|
+
|
|
191
|
+
// 标记加载完成
|
|
192
|
+
window.isNexfepLoadDone = true;
|
|
193
|
+
|
|
194
|
+
// 触发加载完成事件
|
|
195
|
+
window.dispatchEvent(new Event('nexfep-load-done'));
|
|
196
|
+
`;
|
|
197
|
+
console.log("Before inject code INJECT_CODE");
|
|
198
|
+
await this.__injectCode(windowObj, INJECT_CODE);
|
|
199
|
+
console.log("After inject control functions");
|
|
200
|
+
}
|
|
201
|
+
async __createNewWindowObj() {
|
|
202
|
+
const window = this.app.createBrowserWindow({ title: "Nexfep" });
|
|
203
|
+
window.hide();
|
|
204
|
+
this.freeWindowCount++;
|
|
205
|
+
const webview = window.createWebview();
|
|
206
|
+
const windowObj = new Window(window, webview, ++this.windowCount, this);
|
|
207
|
+
this.windows.push(windowObj);
|
|
208
|
+
webview.onIpcMessage((data) => {
|
|
209
|
+
const dataText = data.body.toString();
|
|
210
|
+
const dataObj = JSON.parse(dataText);
|
|
211
|
+
if (dataObj.type == 'NexfepBeforeUnload') {
|
|
212
|
+
webview.evaluateScript(`if(window?.isNexfepLoadDone){
|
|
213
|
+
const MessageBody = { type: 'NexfepBeforeUnload' }
|
|
214
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
215
|
+
}else{
|
|
216
|
+
const MessageBody = { type: 'NexfepLoadFalse' }
|
|
217
|
+
window.ipc.postMessage(JSON.stringify(MessageBody));
|
|
218
|
+
}`);
|
|
219
|
+
}
|
|
220
|
+
else if (dataObj.type == 'NexfepLoadFalse') {
|
|
221
|
+
this.__injectControlFunctions(windowObj);
|
|
222
|
+
}
|
|
223
|
+
else if (dataObj.type == 'NexfepCloseWindow') {
|
|
224
|
+
this.closeWindow(windowObj);
|
|
225
|
+
}
|
|
226
|
+
else if (dataObj.type == 'NexfepMinimizeWindow') {
|
|
227
|
+
window.setMinimized(true);
|
|
228
|
+
}
|
|
229
|
+
else if (dataObj.type == 'NexfepUnMinimizeWindow') {
|
|
230
|
+
window.setMinimized(false);
|
|
231
|
+
}
|
|
232
|
+
else if (dataObj.type == 'NexfepMaximizeWindow') {
|
|
233
|
+
window.setMaximized(true);
|
|
234
|
+
}
|
|
235
|
+
else if (dataObj.type == 'NexfepUnMaximizeWindow') {
|
|
236
|
+
window.setMaximized(false);
|
|
237
|
+
}
|
|
238
|
+
else if (dataObj.type == 'NexfepSetTitle') {
|
|
239
|
+
window.setTitle(dataObj.title);
|
|
240
|
+
}
|
|
241
|
+
else if (dataObj.type == 'NexfepOpenDevTools') {
|
|
242
|
+
webview.openDevtools();
|
|
243
|
+
}
|
|
244
|
+
else if (dataObj.type == 'NexfepCloseDevTools') {
|
|
245
|
+
webview.closeDevtools();
|
|
246
|
+
}
|
|
247
|
+
else if (dataObj.type == 'CustomMessage') {
|
|
248
|
+
this.onCustomMessage(windowObj, dataObj.data);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
return windowObj;
|
|
252
|
+
}
|
|
253
|
+
async createWindow(isShow = true, isDecorated = true) {
|
|
254
|
+
const window = this.windows.find(w => w.isOpen === false) || await this.__createNewWindowObj();
|
|
255
|
+
this.freeWindowCount--;
|
|
256
|
+
if (window) {
|
|
257
|
+
window.isOpen = true;
|
|
258
|
+
window.isShow = isShow;
|
|
259
|
+
window.setDecorated(isDecorated);
|
|
260
|
+
if (isShow) {
|
|
261
|
+
window.show();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (this.freeWindowCount == 0) {
|
|
265
|
+
await this.__createNewWindowObj();
|
|
266
|
+
}
|
|
267
|
+
return window;
|
|
268
|
+
}
|
|
269
|
+
async closeWindow(window) {
|
|
270
|
+
if (window.isOpen) {
|
|
271
|
+
window.isOpen = false;
|
|
272
|
+
window.isShow = false;
|
|
273
|
+
window.setDecorated(true);
|
|
274
|
+
window.hide();
|
|
275
|
+
await window.loadHTML("<!DOCTYPE html><html><head></head><body></body></html>");
|
|
276
|
+
this.freeWindowCount++;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
throw new Error("Window is not open");
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async closePool() {
|
|
283
|
+
this.app.exit();
|
|
284
|
+
}
|
|
285
|
+
mainloop() {
|
|
286
|
+
this.app.run();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
export { WindowPool, Window };
|
|
290
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nexfep",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"packageManager": "pnpm@9.15.9",
|
|
6
|
+
"main": "./index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"compile": "tsc"
|
|
9
|
+
},
|
|
10
|
+
"author": "nexfteam",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=20.11.0"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@webviewjs/webview": "^0.1.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^22.0.0",
|
|
20
|
+
"tsx": "^4.0.0",
|
|
21
|
+
"typescript": "^5.0.0"
|
|
22
|
+
}
|
|
23
|
+
}
|