nexfep 0.6.1 → 0.7.0

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/README-CN.md CHANGED
@@ -63,19 +63,22 @@ const app = new Application();
63
63
  const app = new Application({ WindowsWebview2UserDataFolder: "C:\\custom\\webview2-data" });
64
64
  // 或指定日志文件路径
65
65
  const app = new Application({ LogFilePath: "./app.log" });
66
+ // 或注册自定义协议代理
67
+ const app = new Application({ localProxys: [{ protocolName: "app", localPath: "./public" }] });
66
68
  ```
67
69
 
68
70
  **构造函数参数**
69
71
 
70
- | 选项 | 类型 | 默认值 | 说明 |
71
- | ------------------------------- | ---------------- | ------------------------------------------------ | ----------------------------------- |
72
- | `WindowsWebview2UserDataFolder` | `string`(可选) | `%LOCALAPPDATA%\NexfepDevelopment.webview2-data` | WebView2 用户数据目录(仅 Windows) |
73
- | `LogFilePath` | `string`(可选) | 无 | 日志文件输出路径 |
72
+ | 选项 | 类型 | 默认值 | 说明 |
73
+ | ------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------- |
74
+ | `WindowsWebview2UserDataFolder` | `string`(可选) | `%LOCALAPPDATA%\NexfepDevelopment.webview2-data` | WebView2 用户数据目录(仅 Windows) |
75
+ | `LogFilePath` | `string`(可选) | 无 | 日志文件输出路径 |
76
+ | `localProxys` | `Array<{ protocolName: string; localPath: string }>`(可选) | 无 | 自定义协议代理,用于提供本地文件服务 |
74
77
 
75
78
  **属性**
76
79
 
77
80
  - `windows` — `WindowPool` 实例,用于管理浏览器窗口
78
- - `utils` — 工具方法(如桌面通知)
81
+ - `utils` — 工具方法(如桌面通知、文件选择弹窗)
79
82
  - `logger` — `Logger` 实例,用于日志记录
80
83
 
81
84
  **方法**
@@ -84,7 +87,69 @@ const app = new Application({ LogFilePath: "./app.log" });
84
87
  - `createLocker(appName)` — 创建应用实例锁,防止多个实例运行
85
88
  - `exit()` — 退出应用
86
89
 
87
- ### Icon
90
+ ### 基础类型
91
+
92
+ Nexfep 提供了几个贯穿框架的基础类型。
93
+
94
+ #### Size
95
+
96
+ `Size` 表示窗口尺寸,包含宽度和高度,支持逻辑像素和物理像素。
97
+
98
+ ```typescript
99
+ import { Size } from "nexfep";
100
+
101
+ const size = new Size(800, 600);
102
+ const size = new Size(800, 600, true); // 逻辑像素(默认)
103
+ ```
104
+
105
+ **属性**
106
+
107
+ | 属性 | 类型 | 说明 |
108
+ | -------- | --------- | ----------------------------- |
109
+ | `width` | `number` | 宽度(像素) |
110
+ | `height` | `number` | 高度(像素) |
111
+ | `logical`| `boolean` | 是否使用逻辑像素,默认 `true` |
112
+
113
+ #### Position
114
+
115
+ `Position` 表示窗口在屏幕上的位置,支持逻辑像素和物理像素。
116
+
117
+ ```typescript
118
+ import { Position } from "nexfep";
119
+
120
+ const pos = new Position(100, 100);
121
+ const pos = new Position(100, 100, false); // 物理像素(默认)
122
+ ```
123
+
124
+ **属性**
125
+
126
+ | 属性 | 类型 | 说明 |
127
+ | -------- | --------- | ------------------------------ |
128
+ | `x` | `number` | X 坐标(像素) |
129
+ | `y` | `number` | Y 坐标(像素) |
130
+ | `logical`| `boolean` | 是否使用逻辑像素,默认 `false` |
131
+
132
+ #### WindowLevel
133
+
134
+ `WindowLevel` 是一个枚举,表示窗口的 Z 轴层级。
135
+
136
+ ```typescript
137
+ import { WindowLevel } from "nexfep";
138
+
139
+ window.setLevel(WindowLevel.Bottommost);
140
+ window.setLevel(WindowLevel.Normal);
141
+ window.setLevel(WindowLevel.Topmost);
142
+ ```
143
+
144
+ **值**
145
+
146
+ | 值 | 数值 | 说明 |
147
+ | -------------------------- | ---- | -------- |
148
+ | `WindowLevel.Bottommost` | `-1` | 置底 |
149
+ | `WindowLevel.Normal` | `0` | 正常层级 |
150
+ | `WindowLevel.Topmost` | `1` | 置顶 |
151
+
152
+ #### Icon
88
153
 
89
154
  `Icon` 用于表示图片资源,可被用作窗口图标、托盘图标等。
90
155
 
@@ -233,6 +298,55 @@ const notification = app.utils.notify("标题", { body: "通知内容" });
233
298
  - `options`(可选)— 配置对象
234
299
  - `body` — 通知正文
235
300
 
301
+ ### 选择文件弹窗
302
+
303
+ 通过 `app.utils.openFileDialog()` 打开选择文件弹窗。
304
+
305
+ ```typescript
306
+ const files = await app.utils.openFileDialog({
307
+ multiple: true,
308
+ title: "选择文件",
309
+ filters: [
310
+ { name: "所有文件", extensions: ["*"] },
311
+ { name: "图片文件", extensions: ["jpg", "jpeg", "png"] },
312
+ ],
313
+ });
314
+ ```
315
+
316
+ **参数**
317
+
318
+ - `multiple`(可选)— 是否允许选择多个文件
319
+ - `title`(可选)— 弹窗标题
320
+ - `filters`(可选)— 文件类型过滤器数组,每个元素为一个对象,包含 `name`(过滤器名称)和 `extensions`(支持的文件扩展名数组)
321
+
322
+ **返回值**
323
+
324
+ - `Array<string>`: 选择的文件路径数组
325
+
326
+ ### 本地协议代理
327
+
328
+ Nexfep 支持注册自定义协议处理器,用于提供本地文件服务,从而可以通过自定义协议(如 `app://`)加载本地资源。
329
+
330
+ ```typescript
331
+ const app = new Application({
332
+ localProxys: [
333
+ { protocolName: "app", localPath: "./public" },
334
+ ],
335
+ });
336
+ ```
337
+
338
+ 注册后,即可使用自定义协议加载页面:
339
+
340
+ ```typescript
341
+ await window.loadURL("app://index.html");
342
+ ```
343
+
344
+ 对 `app://` 的请求会被拦截,并从 `./public` 目录中提供对应的文件。MIME 类型会根据文件扩展名自动检测。
345
+
346
+ **安全**
347
+
348
+ 协议代理使用路径解析来防止目录遍历攻击。如果请求试图访问配置的 `localPath` 之外的文件,将返回 `403 Forbidden` 响应。
349
+
236
350
  ### 窗口池
237
351
 
238
352
  `WindowPool` 是框架的核心管理类,负责窗口的创建和回收。
@@ -260,22 +374,22 @@ const win = await pool.createWindow({
260
374
  title: "我的应用", // 窗口标题,默认 "Nexfep Window"
261
375
  icon: iconInstance, // 窗口图标,Icon 实例,可选
262
376
  resizable: true, // 是否可调整大小,默认 true
263
- width: 800, // 窗口宽度,默认 800
264
- height: 600, // 窗口高度,默认 600
377
+ size: new Size(800, 600), // 窗口尺寸,可选
378
+ position: new Position(100, 100), // 窗口位置,可选
265
379
  });
266
380
  ```
267
381
 
268
382
  **参数说明**
269
383
 
270
- | 选项 | 类型 | 默认值 | 说明 |
271
- | ------------ | --------- | ----------------- | ------------------------------------------------------------------- |
272
- | `visible` | `boolean` | `true` | 是否立即显示窗口 |
273
- | `decoration` | `boolean` | `true` | 是否使用系统窗口装饰。设为 `false` 时,窗口无边框,需要自定义标题栏 |
274
- | `title` | `string` | `"Nexfep Window"` | 窗口标题 |
275
- | `icon` | `Icon` | 无 | 窗口图标 |
276
- | `resizable` | `boolean` | `true` | 窗口是否可调整大小 |
277
- | `width` | `number` | `800` | 窗口宽度(像素) |
278
- | `height` | `number` | `600` | 窗口高度(像素) |
384
+ | 选项 | 类型 | 默认值 | 说明 |
385
+ | ------------ | ----------- | ----------------- | ------------------------------------------------------------------- |
386
+ | `visible` | `boolean` | `true` | 是否立即显示窗口 |
387
+ | `decoration` | `boolean` | `true` | 是否使用系统窗口装饰。设为 `false` 时,窗口无边框,需要自定义标题栏 |
388
+ | `title` | `string` | `"Nexfep Window"` | 窗口标题 |
389
+ | `icon` | `Icon` | 无 | 窗口图标 |
390
+ | `resizable` | `boolean` | `true` | 窗口是否可调整大小 |
391
+ | `size` | `Size` | 无 | 窗口尺寸,可选 |
392
+ | `position` | `Position` | 无 | 窗口位置,可选 |
279
393
 
280
394
  ### 窗口操作
281
395
 
@@ -287,7 +401,8 @@ window.minimize();
287
401
  window.close();
288
402
  window.focus();
289
403
  window.setTitle("新标题");
290
- window.setSize(800, 600);
404
+ window.setSize(new Size(800, 600));
405
+ window.setPosition(new Position(100, 100));
291
406
  window.openDevTools();
292
407
  ```
293
408
 
@@ -640,11 +755,12 @@ nexfep build -u 7
640
755
 
641
756
  | 方法/属性 | 参数 | 返回值 | 说明 |
642
757
  | ----------------------- | -------------------------------------------------- | ----------- | ---------------- |
643
- | `constructor(options?)` | `{ WindowsWebview2UserDataFolder?, LogFilePath? }` | Application | 创建应用实例 |
758
+ | `constructor(options?)` | `{ WindowsWebview2UserDataFolder?, LogFilePath?, localProxys? }` | Application | 创建应用实例 |
644
759
  | `windows` | / | WindowPool | 窗口池实例 |
645
- | `utils` | / | \_\_Utils | 工具方法(通知) |
760
+ | `utils` | / | \_\_Utils | 工具方法(通知、文件选择弹窗) |
646
761
  | `logger` | / | Logger | 日志实例 |
647
762
  | `createTray(options)` | 见 Tray 章节 | Tray | 创建系统托盘图标 |
763
+ | `createLocker(appName)` | `appName`: string | Locker | 创建应用实例锁 |
648
764
  | `exit()` | 无 | void | 退出应用 |
649
765
 
650
766
  ### WindowPool
@@ -656,7 +772,8 @@ nexfep build -u 7
656
772
  | `unhandle(event, callback)` | `event`: string, `callback`: (data: any) => any | 无 | 取消监听指定事件 |
657
773
  | `global` | / | Map\<string, any> | 全局变量 Map |
658
774
  | `closeWindow(window)` | `window`: Window | Promise\<void> | 关闭指定窗口并回收至池中 |
659
- | `onCustomMessage` | `(window: Window, data: string) => void` | 无 | 自定义消息回调函数 |
775
+ | `onCustomMessage` | `(window: Window, message: string, data: any) => void` | 无 | 自定义消息回调函数 |
776
+ | `broadcast(event, data)` | `event`: string, `data`: any | 无 | 向所有打开的窗口发送事件 |
660
777
 
661
778
  ### Window
662
779
 
@@ -674,19 +791,20 @@ nexfep build -u 7
674
791
  | `setTitle(title)` | `title`: string | void | 设置窗口标题 |
675
792
  | `setDecorated(isDecorated)` | `isDecorated`: boolean | void | 设置窗口是否带边框和标题栏 |
676
793
  | `setResizable(resizable)` | `resizable`: boolean | void | 设置窗口是否可调整大小 |
677
- | `setLevel(level)` | `level`: `-1` \| `0` \| `1` | void | 设置窗口层级:-1=置底,0=正常,1=置顶 |
794
+ | `setLevel(level)` | `level`: `WindowLevel` | void | 设置窗口层级:Bottommost、Normal、Topmost |
678
795
  | `setFullScreen(isFullScreen, options?)` | `isFullScreen`: `boolean`, `options?`: `{ borderless?: boolean }` | void | 设置全屏模式。`borderless: true` 为无边框全屏,否则为独占全屏 |
679
796
  | `setIcon(icon)` | `icon`: `Icon` | void | 设置窗口图标 |
680
- | `setSize(width, height)` | `width`: number, `height`: number | void | 设置窗口尺寸(像素) |
681
- | `getSize()` | 无 | { width: number, height: number } | 获取窗口尺寸(像素) |
682
- | `setPosition(x, y)` | `x`: number, `y`: number | void | 设置窗口位置(像素) |
683
- | `getPosition()` | 无 | { x: number, y: number } | 获取窗口位置(像素) |
797
+ | `setSize(size)` | `size`: `Size` | void | 设置窗口尺寸 |
798
+ | `getSize(logical?)` | `logical?`: `boolean` | { width: number, height: number } | 获取窗口尺寸(像素) |
799
+ | `setPosition(position)` | `position`: `Position` | void | 设置窗口位置 |
800
+ | `getPosition(logical?)` | `logical?`: `boolean` | { x: number, y: number } | 获取窗口位置(像素) |
684
801
  | `focus()` | 无 | void | 窗口获取焦点 |
685
802
  | `isFocused()` | 无 | boolean | 是否有焦点 |
686
803
  | `isMaximized()` | 无 | boolean | 是否最大化 |
687
804
  | `isMinimized()` | 无 | boolean | 是否最小化 |
688
805
  | `toggleMaximize()` | 无 | void | 切换最大化状态 |
689
806
  | `toggleMinimize()` | 无 | void | 切换最小化状态 |
807
+ | `tell(message, data)` | `message`: string, `data`: any | void | 从主进程向该窗口发送消息 |
690
808
  | `openDevTools()` | 无 | void | 打开开发者工具 |
691
809
  | `closeDevTools()` | 无 | void | 关闭开发者工具 |
692
810
  | `id` | 无 | number | 窗口唯一标识,自增编号 |
package/README.md CHANGED
@@ -63,19 +63,22 @@ const app = new Application();
63
63
  const app = new Application({ WindowsWebview2UserDataFolder: "C:\\custom\\webview2-data" });
64
64
  // or with log file path
65
65
  const app = new Application({ LogFilePath: "./app.log" });
66
+ // or with custom protocol proxy
67
+ const app = new Application({ localProxys: [{ protocolName: "app", localPath: "./public" }] });
66
68
  ```
67
69
 
68
70
  **Constructor Options**
69
71
 
70
- | Option | Type | Default | Description |
71
- | ------------------------------- | ------------------- | ------------------------------------------------ | ------------------------------------------- |
72
- | `WindowsWebview2UserDataFolder` | `string` (optional) | `%LOCALAPPDATA%\NexfepDevelopment.webview2-data` | WebView2 user data directory (Windows only) |
73
- | `LogFilePath` | `string` (optional) | none | File path for log output |
72
+ | Option | Type | Default | Description |
73
+ | ------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------ | ------------------------------------------- |
74
+ | `WindowsWebview2UserDataFolder` | `string` (optional) | `%LOCALAPPDATA%\NexfepDevelopment.webview2-data` | WebView2 user data directory (Windows only) |
75
+ | `LogFilePath` | `string` (optional) | none | File path for log output |
76
+ | `localProxys` | `Array<{ protocolName: string; localPath: string }>` (optional) | none | Custom protocol proxies for serving local files |
74
77
 
75
78
  **Properties**
76
79
 
77
80
  - `windows` — The `WindowPool` instance for managing browser windows
78
- - `utils` — Utility methods (e.g., desktop notifications)
81
+ - `utils` — Utility methods (e.g., desktop notifications, file dialogs)
79
82
  - `logger` — The `Logger` instance for logging
80
83
 
81
84
  **Methods**
@@ -84,7 +87,69 @@ const app = new Application({ LogFilePath: "./app.log" });
84
87
  - `createLocker(appName)` — Create an application instance lock to prevent multiple instances
85
88
  - `exit()` — Exit the application
86
89
 
87
- ### Icon
90
+ ### Basic Types
91
+
92
+ Nexfep provides several basic types that are used throughout the framework.
93
+
94
+ #### Size
95
+
96
+ `Size` represents a window size with width and height, supporting both logical and physical pixels.
97
+
98
+ ```typescript
99
+ import { Size } from "nexfep";
100
+
101
+ const size = new Size(800, 600);
102
+ const size = new Size(800, 600, true); // logical pixels (default)
103
+ ```
104
+
105
+ **Properties**
106
+
107
+ | Property | Type | Description |
108
+ | -------- | -------- | ------------------------------------------------------- |
109
+ | `width` | `number` | Width in pixels |
110
+ | `height` | `number` | Height in pixels |
111
+ | `logical`| `boolean`| Whether to use logical pixels (DPI-aware), default `true` |
112
+
113
+ #### Position
114
+
115
+ `Position` represents a window position on screen, supporting both logical and physical pixels.
116
+
117
+ ```typescript
118
+ import { Position } from "nexfep";
119
+
120
+ const pos = new Position(100, 100);
121
+ const pos = new Position(100, 100, false); // physical pixels (default)
122
+ ```
123
+
124
+ **Properties**
125
+
126
+ | Property | Type | Description |
127
+ | -------- | -------- | -------------------------------------------------------- |
128
+ | `x` | `number` | X position in pixels |
129
+ | `y` | `number` | Y position in pixels |
130
+ | `logical`| `boolean`| Whether to use logical pixels (DPI-aware), default `false` |
131
+
132
+ #### WindowLevel
133
+
134
+ `WindowLevel` is an enum representing the window z-order level.
135
+
136
+ ```typescript
137
+ import { WindowLevel } from "nexfep";
138
+
139
+ window.setLevel(WindowLevel.Bottommost);
140
+ window.setLevel(WindowLevel.Normal);
141
+ window.setLevel(WindowLevel.Topmost);
142
+ ```
143
+
144
+ **Values**
145
+
146
+ | Value | Numeric | Description |
147
+ | --------------------------- | ------- | ----------------- |
148
+ | `WindowLevel.Bottommost` | `-1` | Always on bottom |
149
+ | `WindowLevel.Normal` | `0` | Normal level |
150
+ | `WindowLevel.Topmost` | `1` | Always on top |
151
+
152
+ #### Icon
88
153
 
89
154
  `Icon` represents an image resource that can be used as a window icon, tray icon, etc.
90
155
 
@@ -231,6 +296,55 @@ const notification = app.utils.notify("Title", { body: "Notification body" });
231
296
  - `options` (optional) — Configuration object
232
297
  - `body` — Notification body text
233
298
 
299
+ ### File Selection Dialog
300
+
301
+ Open the file selection dialog using `app.utils.openFileDialog()`.
302
+
303
+ ```typescript
304
+ const files = await app.utils.openFileDialog({
305
+ multiple: true,
306
+ title: "Select Files",
307
+ filters: [
308
+ { name: "All Files", extensions: ["*"] },
309
+ { name: "Image Files", extensions: ["jpg", "jpeg", "png"] },
310
+ ],
311
+ });
312
+ ```
313
+
314
+ **Parameters**
315
+
316
+ - `multiple` (Optional) — Allow selection of multiple files
317
+ - `title` (Optional) — Title of the dialog
318
+ - `filters` (Optional) — Array of file type filters, each object has `name` (filter name) and `extensions` (supported file extensions array)
319
+
320
+ **Return Value**
321
+
322
+ - `Array<string>`: Array of selected file paths
323
+
324
+ ### Local Protocol Proxy
325
+
326
+ Nexfep supports registering custom protocol handlers to serve local files, allowing you to load local resources using custom protocols like `app://`.
327
+
328
+ ```typescript
329
+ const app = new Application({
330
+ localProxys: [
331
+ { protocolName: "app", localPath: "./public" },
332
+ ],
333
+ });
334
+ ```
335
+
336
+ After registration, you can load pages using the custom protocol:
337
+
338
+ ```typescript
339
+ await window.loadURL("app://index.html");
340
+ ```
341
+
342
+ Requests to `app://` will be intercepted and the corresponding files from the `./public` directory will be served. The MIME type is automatically detected based on the file extension.
343
+
344
+ **Security**
345
+
346
+ The proxy uses path resolution to prevent directory traversal attacks. If a request tries to access files outside the configured `localPath`, it will return a `403 Forbidden` response.
347
+
234
348
  ### Window Pool
235
349
 
236
350
  `WindowPool` is the core management class of the framework, responsible for window creation and recycling.
@@ -258,22 +372,22 @@ const win = await pool.createWindow({
258
372
  title: "My App", // Window title, default "Nexfep Window"
259
373
  icon: iconInstance, // Window icon, Icon instance, optional
260
374
  resizable: true, // Whether the window is resizable, default true
261
- width: 800, // Window width, default 800
262
- height: 600, // Window height, default 600
375
+ size: new Size(800, 600), // Window size, optional
376
+ position: new Position(100, 100), // Window position, optional
263
377
  });
264
378
  ```
265
379
 
266
380
  **Parameters**
267
381
 
268
- | Option | Type | Default | Description |
269
- | ------------ | --------- | ----------------- | ---------------------------------------------------------------------------------------------------------------- |
270
- | `visible` | `boolean` | `true` | Whether to immediately show the window |
271
- | `decoration` | `boolean` | `true` | Whether to use system window decorations. When `false`, the window has no border and requires a custom title bar |
272
- | `title` | `string` | `"Nexfep Window"` | Window title |
273
- | `icon` | `Icon` | none | Window icon |
274
- | `resizable` | `boolean` | `true` | Whether the window is resizable |
275
- | `width` | `number` | `800` | Window width in pixels |
276
- | `height` | `number` | `600` | Window height in pixels |
382
+ | Option | Type | Default | Description |
383
+ | ------------ | ----------- | ----------------- | ---------------------------------------------------------------------------------------------------------------- |
384
+ | `visible` | `boolean` | `true` | Whether to immediately show the window |
385
+ | `decoration` | `boolean` | `true` | Whether to use system window decorations. When `false`, the window has no border and requires a custom title bar |
386
+ | `title` | `string` | `"Nexfep Window"` | Window title |
387
+ | `icon` | `Icon` | none | Window icon |
388
+ | `resizable` | `boolean` | `true` | Whether the window is resizable |
389
+ | `size` | `Size` | none | Window size, optional |
390
+ | `position` | `Position` | none | Window position, optional |
277
391
 
278
392
  ### Window Operations
279
393
 
@@ -285,7 +399,8 @@ window.minimize();
285
399
  window.close();
286
400
  window.focus();
287
401
  window.setTitle("New Title");
288
- window.setSize(800, 600);
402
+ window.setSize(new Size(800, 600));
403
+ window.setPosition(new Position(100, 100));
289
404
  window.openDevTools();
290
405
  ```
291
406
 
@@ -638,11 +753,12 @@ Please do not include the outer `metadata` field, just the internal fields. Like
638
753
 
639
754
  | Method/Property | Parameters | Return Value | Description |
640
755
  | ----------------------- | -------------------------------------------------- | ------------ | -------------------------------- |
641
- | `constructor(options?)` | `{ WindowsWebview2UserDataFolder?, LogFilePath? }` | Application | Creates the application instance |
756
+ | `constructor(options?)` | `{ WindowsWebview2UserDataFolder?, LogFilePath?, localProxys? }` | Application | Creates the application instance |
642
757
  | `windows` | / | WindowPool | The window pool instance |
643
- | `utils` | / | \_\_Utils | Utility methods (notifications) |
758
+ | `utils` | / | \_\_Utils | Utility methods (notifications, file dialogs) |
644
759
  | `logger` | / | Logger | The logger instance |
645
760
  | `createTray(options)` | see Tray section | Tray | Creates a system tray icon |
761
+ | `createLocker(appName)` | `appName`: string | Locker | Creates an application lock |
646
762
  | `exit()` | None | void | Exits the application |
647
763
 
648
764
  ### WindowPool
@@ -654,7 +770,8 @@ Please do not include the outer `metadata` field, just the internal fields. Like
654
770
  | `unhandle(event, callback)` | `event`: string, `callback`: (data: any) => any | None | Removes the specified event listener |
655
771
  | `global` | / | Map\<string, any> | A global variable map |
656
772
  | `closeWindow(window)` | `window`: Window | Promise\<void> | Closes the specified window and returns it to the pool |
657
- | `onCustomMessage` | `(window: Window, data: string) => void` | None | Custom message callback |
773
+ | `onCustomMessage` | `(window: Window, message: string, data: any) => void` | None | Custom message callback |
774
+ | `broadcast(event, data)` | `event`: string, `data`: any | None | Sends an event to all open windows |
658
775
 
659
776
  ### Window
660
777
 
@@ -672,19 +789,20 @@ Please do not include the outer `metadata` field, just the internal fields. Like
672
789
  | `setTitle(title)` | `title`: string | void | Sets the window title |
673
790
  | `setDecorated(isDecorated)` | `isDecorated`: boolean | void | Sets whether the window has borders and title bar |
674
791
  | `setResizable(resizable)` | `resizable`: boolean | void | Sets whether the window is resizable |
675
- | `setLevel(level)` | `level`: `-1` \| `0` \| `1` | void | Sets window level: -1=bottom, 0=normal, 1=top |
792
+ | `setLevel(level)` | `level`: `WindowLevel` | void | Sets window level: Bottommost, Normal, Topmost |
676
793
  | `setFullScreen(isFullScreen, options?)` | `isFullScreen`: `boolean`, `options?`: `{ borderless?: boolean }` | void | Sets fullscreen mode. `borderless: true` for borderless fullscreen, otherwise exclusive fullscreen |
677
794
  | `setIcon(icon)` | `icon`: `Icon` | void | Sets the window icon |
678
- | `setSize(width, height)` | `width`: number, `height`: number | void | Sets the window size in pixels |
679
- | `getSize()` | None | { width: number, height: number } | Gets the window size in pixels |
680
- | `setPosition(x, y)` | `x`: number, `y`: number | void | Sets the window position in pixels |
681
- | `getPosition()` | None | { x: number, y: number } | Gets the window position in pixels |
795
+ | `setSize(size)` | `size`: `Size` | void | Sets the window size |
796
+ | `getSize(logical?)` | `logical?`: `boolean` | { width: number, height: number } | Gets the window size in pixels |
797
+ | `setPosition(position)` | `position`: `Position` | void | Sets the window position |
798
+ | `getPosition(logical?)` | `logical?`: `boolean` | { x: number, y: number } | Gets the window position in pixels |
682
799
  | `focus()` | None | void | Focuses the window |
683
800
  | `isFocused()` | None | boolean | Whether the window has focus |
684
801
  | `isMaximized()` | None | boolean | Whether the window is maximized |
685
802
  | `isMinimized()` | None | boolean | Whether the window is minimized |
686
803
  | `toggleMaximize()` | None | void | Toggles the window maximized state |
687
804
  | `toggleMinimize()` | None | void | Toggles the window minimized state |
805
+ | `tell(message, data)` | `message`: string, `data`: any | void | Sends a message to the window from the main process |
688
806
  | `openDevTools()` | None | void | Opens developer tools |
689
807
  | `closeDevTools()` | None | void | Closes developer tools |
690
808
  | `id` | None | number | Unique window identifier, auto-incrementing |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexfep",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "A desktop application framework based on @webviewjs/webview",
5
5
  "keywords": [
6
6
  "@webviewjs/webview",
@@ -41,8 +41,8 @@
41
41
  "src/Logger.d.ts",
42
42
  "src/SingleInstance.js",
43
43
  "src/SingleInstance.d.ts",
44
- "src/Icon.js",
45
- "src/Icon.d.ts",
44
+ "src/Basics.js",
45
+ "src/Basics.d.ts",
46
46
  "frontend/index.d.ts"
47
47
  ],
48
48
  "type": "module",
@@ -66,7 +66,8 @@
66
66
  },
67
67
  "dependencies": {
68
68
  "@nexfteam/single-instance": "0.0.8",
69
- "@webviewjs/webview": "0.4.1"
69
+ "@webviewjs/webview": "0.4.3",
70
+ "mrmime": "^2.0.1"
70
71
  },
71
72
  "devDependencies": {
72
73
  "@types/node": "^22.0.0",
@@ -3,13 +3,21 @@ import { WindowPool } from "./WindowManager.js";
3
3
  import { Tray } from "./Tray.js";
4
4
  import { Logger } from "./Logger.js";
5
5
  import { Locker } from "./SingleInstance.js";
6
- import { Icon } from "./Icon.js";
6
+ import { Icon } from "./Basics.js";
7
7
  declare class __Utils {
8
- app: WebviewApplication;
9
- constructor(app: WebviewApplication);
8
+ app: Application;
9
+ constructor(app: Application);
10
10
  notify(title: string, options?: {
11
11
  body?: string;
12
12
  }): Notification;
13
+ openFileDialog(options?: {
14
+ multiple?: boolean;
15
+ title?: string;
16
+ filters?: Array<{
17
+ name: string;
18
+ extensions: Array<string>;
19
+ }>;
20
+ }): Promise<string[]>;
13
21
  }
14
22
  declare class Application {
15
23
  app: WebviewApplication;
@@ -19,6 +27,10 @@ declare class Application {
19
27
  constructor(options?: {
20
28
  WindowsWebview2UserDataFolder?: string;
21
29
  LogFilePath?: string;
30
+ localProxys?: Array<{
31
+ protocolName: string;
32
+ localPath: string;
33
+ }>;
22
34
  });
23
35
  createTray(options: {
24
36
  id: string;
@@ -32,4 +44,5 @@ declare class Application {
32
44
  createLocker(appName: string): Locker;
33
45
  exit(): void;
34
46
  }
35
- export { Application, Icon };
47
+ export { Application };
48
+ export * from "./Basics.js";
@@ -3,7 +3,6 @@ import { WindowPool } from "./WindowManager.js";
3
3
  import { Tray } from "./Tray.js";
4
4
  import { Logger } from "./Logger.js";
5
5
  import { Locker } from "./SingleInstance.js";
6
- import { Icon } from "./Icon.js";
7
6
  class __Utils {
8
7
  app;
9
8
  constructor(app) {
@@ -13,6 +12,12 @@ class __Utils {
13
12
  const notification = new Notification(title, { body: options?.body });
14
13
  return notification;
15
14
  }
15
+ async openFileDialog(options) {
16
+ const tempWin = await this.app.windows.createWindow({ visible: false });
17
+ const result = tempWin.window.openFileDialog(options);
18
+ tempWin.close();
19
+ return result;
20
+ }
16
21
  }
17
22
  class Application {
18
23
  app;
@@ -21,11 +26,12 @@ class Application {
21
26
  utils;
22
27
  constructor(options = {}) {
23
28
  this.app = new WebviewApplication();
24
- this.utils = new __Utils(this.app);
29
+ this.utils = new __Utils(this);
25
30
  this.logger = new Logger(options.LogFilePath);
26
31
  const poolOptions = {
27
32
  WindowsWebview2UserDataFolder: options.WindowsWebview2UserDataFolder,
28
33
  logger: this.logger,
34
+ localProxys: options.localProxys,
29
35
  };
30
36
  this.windows = new WindowPool(this.app, poolOptions);
31
37
  this.app.whenReady();
@@ -40,4 +46,5 @@ class Application {
40
46
  this.app.exit();
41
47
  }
42
48
  }
43
- export { Application, Icon };
49
+ export { Application };
50
+ export * from "./Basics.js";
@@ -16,4 +16,21 @@ declare class Icon {
16
16
  toTrayIconImage(): TrayIconImage;
17
17
  toBuffer(): Buffer;
18
18
  }
19
- export { Icon };
19
+ declare class Size {
20
+ width: number;
21
+ height: number;
22
+ logical: boolean;
23
+ constructor(width: number, height: number, logical?: boolean);
24
+ }
25
+ declare class Position {
26
+ x: number;
27
+ y: number;
28
+ logical: boolean;
29
+ constructor(x: number, y: number, logical?: boolean);
30
+ }
31
+ declare enum WindowLevel {
32
+ Bottommost = -1,
33
+ Normal = 0,
34
+ Topmost = 1
35
+ }
36
+ export { Icon, Size, Position, WindowLevel };
@@ -33,4 +33,30 @@ class Icon {
33
33
  return Buffer.from(this.data);
34
34
  }
35
35
  }
36
- export { Icon };
36
+ class Size {
37
+ width;
38
+ height;
39
+ logical;
40
+ constructor(width, height, logical) {
41
+ this.width = width;
42
+ this.height = height;
43
+ this.logical = logical ?? true;
44
+ }
45
+ }
46
+ class Position {
47
+ x;
48
+ y;
49
+ logical;
50
+ constructor(x, y, logical) {
51
+ this.x = x;
52
+ this.y = y;
53
+ this.logical = logical ?? false;
54
+ }
55
+ }
56
+ var WindowLevel;
57
+ (function (WindowLevel) {
58
+ WindowLevel[WindowLevel["Bottommost"] = -1] = "Bottommost";
59
+ WindowLevel[WindowLevel["Normal"] = 0] = "Normal";
60
+ WindowLevel[WindowLevel["Topmost"] = 1] = "Topmost";
61
+ })(WindowLevel || (WindowLevel = {}));
62
+ export { Icon, Size, Position, WindowLevel };
package/src/Tray.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Application, TrayIcon } from "@webviewjs/webview";
2
- import { Icon } from "./Icon.js";
2
+ import { Icon } from "./Basics.js";
3
3
  declare class Tray {
4
4
  app: Application;
5
5
  tray: TrayIcon;
@@ -1,6 +1,6 @@
1
1
  import { Application, BrowserWindow, Webview } from "@webviewjs/webview";
2
2
  import { Logger } from "./Logger.js";
3
- import { Icon } from "./Icon.js";
3
+ import { Icon, Size, Position, WindowLevel } from "./Basics.js";
4
4
  declare class Window {
5
5
  window: BrowserWindow;
6
6
  webview: Webview;
@@ -10,7 +10,7 @@ declare class Window {
10
10
  id: number;
11
11
  private myPool;
12
12
  constructor(window: BrowserWindow, webview: Webview, id: number, myPool: WindowPool);
13
- setLevel(level: -1 | 0 | 1): void;
13
+ setLevel(level: WindowLevel): void;
14
14
  setFullScreen(isFullScreen: boolean, options?: {
15
15
  borderless?: boolean;
16
16
  }): void;
@@ -28,10 +28,10 @@ declare class Window {
28
28
  openDevTools(): void;
29
29
  closeDevTools(): void;
30
30
  setIcon(icon: Icon): void;
31
- setSize(width: number, height: number): void;
32
- getSize(): import("@webviewjs/webview").Dimensions;
33
- setPosition(x: number, y: number): void;
34
- getPosition(): import("@webviewjs/webview").Position;
31
+ setSize(size: Size): void;
32
+ getSize(logical?: boolean): import("@webviewjs/webview").Dimensions;
33
+ setPosition(position: Position): void;
34
+ getPosition(logical?: boolean): import("@webviewjs/webview").Position;
35
35
  isFocused(): boolean;
36
36
  focus(): void;
37
37
  tell(message: string, data: any): void;
@@ -50,11 +50,17 @@ declare class WindowPool {
50
50
  private injectCount;
51
51
  private windowCount;
52
52
  private freeWindowCount;
53
+ private localProxys;
53
54
  global: Map<string, any>;
54
55
  constructor(app: Application, options: {
55
56
  WindowsWebview2UserDataFolder?: string;
56
57
  logger?: Logger;
58
+ localProxys?: Array<{
59
+ protocolName: string;
60
+ localPath: string;
61
+ }>;
57
62
  });
63
+ __registryLocalProxy(window: BrowserWindow, protocolName: string, localPath: string): void;
58
64
  __injectCode(window: Window, code: string): Promise<void>;
59
65
  __injectControlFunctions(windowObj: Window): Promise<void>;
60
66
  __createNewWindowObj(): Promise<Window>;
@@ -64,8 +70,8 @@ declare class WindowPool {
64
70
  title?: string;
65
71
  icon?: Icon;
66
72
  resizable?: boolean;
67
- width?: number;
68
- height?: number;
73
+ size?: Size;
74
+ position?: Position;
69
75
  }): Promise<Window>;
70
76
  closeWindow(window: Window): Promise<void>;
71
77
  handle(event: string, callback: (data: any) => any): Promise<void>;
@@ -1,7 +1,9 @@
1
1
  import { Logger } from "./Logger.js";
2
+ import { WindowLevel } from "./Basics.js";
2
3
  import os from "os";
3
4
  import path from "path";
4
5
  import fs from "fs";
6
+ import { lookup } from "mrmime";
5
7
  class Window {
6
8
  window;
7
9
  webview;
@@ -20,17 +22,17 @@ class Window {
20
22
  this.id = id;
21
23
  }
22
24
  setLevel(level) {
23
- if (level == -1) {
25
+ if (level == WindowLevel.Bottommost) {
24
26
  this.window.setAlwaysOnTop(false);
25
27
  this.window.setAlwaysOnBottom(true);
26
28
  return;
27
29
  }
28
- else if (level == 0) {
30
+ else if (level == WindowLevel.Normal) {
29
31
  this.window.setAlwaysOnTop(false);
30
32
  this.window.setAlwaysOnBottom(false);
31
33
  return;
32
34
  }
33
- else if (level == 1) {
35
+ else if (level == WindowLevel.Topmost) {
34
36
  this.window.setAlwaysOnTop(true);
35
37
  this.window.setAlwaysOnBottom(false);
36
38
  return;
@@ -102,17 +104,17 @@ class Window {
102
104
  setIcon(icon) {
103
105
  this.window.setWindowIcon(icon.data, icon.width, icon.height);
104
106
  }
105
- setSize(width, height) {
106
- this.window.setSize(width, height);
107
+ setSize(size) {
108
+ this.window.setSize(size.width, size.height, size.logical);
107
109
  }
108
- getSize() {
109
- return this.window.getInnerSize();
110
+ getSize(logical) {
111
+ return this.window.getInnerSize(logical);
110
112
  }
111
- setPosition(x, y) {
112
- this.window.setPosition(x, y);
113
+ setPosition(position) {
114
+ this.window.setPosition(position.x, position.y, position.logical);
113
115
  }
114
- getPosition() {
115
- return this.window.getPosition();
116
+ getPosition(logical) {
117
+ return this.window.getPosition(logical);
116
118
  }
117
119
  isFocused() {
118
120
  return this.window.isFocused();
@@ -153,6 +155,7 @@ class WindowPool {
153
155
  injectCount;
154
156
  windowCount;
155
157
  freeWindowCount;
158
+ localProxys;
156
159
  global;
157
160
  constructor(app, options) {
158
161
  if (os.platform() === "win32") {
@@ -174,6 +177,36 @@ class WindowPool {
174
177
  this.freeWindowCount = 0;
175
178
  this.global = new Map();
176
179
  this.logger = options.logger || new Logger();
180
+ this.localProxys = options.localProxys || [];
181
+ }
182
+ __registryLocalProxy(window, protocolName, localPath) {
183
+ const basePath = path.resolve(localPath);
184
+ console.log(window.registerProtocol);
185
+ window.registerProtocol(protocolName, async (request) => {
186
+ try {
187
+ const url = new URL(request.url);
188
+ let relativePath = url.pathname;
189
+ if (relativePath === "/" || relativePath === "") {
190
+ relativePath = "/index.html";
191
+ }
192
+ const absolutePath = path.join(basePath, relativePath);
193
+ const resolvedPath = path.resolve(absolutePath);
194
+ if (!resolvedPath.startsWith(basePath)) {
195
+ return new Response("Forbidden", { status: 403 });
196
+ }
197
+ const content = fs.readFileSync(resolvedPath);
198
+ const mimeType = lookup(resolvedPath) || "application/octet-stream";
199
+ return new Response(content, {
200
+ status: 200,
201
+ headers: {
202
+ "Content-Type": mimeType,
203
+ },
204
+ });
205
+ }
206
+ catch {
207
+ return new Response("Not Found", { status: 404 });
208
+ }
209
+ });
177
210
  }
178
211
  async __injectCode(window, code) {
179
212
  await window.webview.evaluateScript(code);
@@ -365,6 +398,9 @@ class WindowPool {
365
398
  visible: false,
366
399
  focused: false,
367
400
  });
401
+ this.localProxys.forEach((proxy) => {
402
+ this.__registryLocalProxy(window, proxy.protocolName, proxy.localPath);
403
+ });
368
404
  this.freeWindowCount++;
369
405
  const webview = window.createWebview();
370
406
  const windowObj = new Window(window, webview, ++this.windowCount, this);
@@ -486,7 +522,12 @@ class WindowPool {
486
522
  window.setIcon(options?.icon);
487
523
  }
488
524
  window.setResizable(options?.resizable ?? true);
489
- window.setSize(options?.width ?? 800, options?.height ?? 600);
525
+ if (options?.size) {
526
+ window.setSize(options?.size);
527
+ }
528
+ if (options?.position) {
529
+ window.setPosition(options?.position);
530
+ }
490
531
  }
491
532
  if (this.freeWindowCount == 0) {
492
533
  this.__createNewWindowObj();