electrobun 0.0.19-beta.118 → 0.0.19-beta.119

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.
Files changed (68) hide show
  1. package/README.md +1 -1
  2. package/dist/api/browser/webviewtag.ts +54 -2
  3. package/dist/api/bun/ElectrobunConfig.ts +171 -0
  4. package/dist/api/bun/core/BrowserWindow.ts +4 -0
  5. package/dist/api/bun/core/Tray.ts +14 -0
  6. package/dist/api/bun/index.ts +2 -0
  7. package/dist/api/bun/proc/native.ts +76 -5
  8. package/package.json +4 -2
  9. package/src/cli/index.ts +43 -17
  10. package/templates/hello-world/electrobun.config.ts +28 -0
  11. package/templates/hello-world/package.json +1 -1
  12. package/templates/interactive-playground/README.md +26 -0
  13. package/templates/interactive-playground/assets/tray-icon.png +0 -0
  14. package/templates/interactive-playground/electrobun.config.ts +36 -0
  15. package/templates/interactive-playground/package-lock.json +36 -0
  16. package/templates/interactive-playground/package.json +15 -0
  17. package/templates/interactive-playground/src/bun/demos/files.ts +70 -0
  18. package/templates/interactive-playground/src/bun/demos/menus.ts +139 -0
  19. package/templates/interactive-playground/src/bun/demos/rpc.ts +83 -0
  20. package/templates/interactive-playground/src/bun/demos/system.ts +72 -0
  21. package/templates/interactive-playground/src/bun/demos/updates.ts +105 -0
  22. package/templates/interactive-playground/src/bun/demos/windows.ts +90 -0
  23. package/templates/interactive-playground/src/bun/index.ts +124 -0
  24. package/templates/interactive-playground/src/bun/types/rpc.ts +109 -0
  25. package/templates/interactive-playground/src/mainview/components/EventLog.ts +107 -0
  26. package/templates/interactive-playground/src/mainview/components/Sidebar.ts +65 -0
  27. package/templates/interactive-playground/src/mainview/components/Toast.ts +57 -0
  28. package/templates/interactive-playground/src/mainview/demos/FileDemo.ts +211 -0
  29. package/templates/interactive-playground/src/mainview/demos/MenuDemo.ts +102 -0
  30. package/templates/interactive-playground/src/mainview/demos/RPCDemo.ts +229 -0
  31. package/templates/interactive-playground/src/mainview/demos/TrayDemo.ts +132 -0
  32. package/templates/interactive-playground/src/mainview/demos/WebViewDemo.ts +411 -0
  33. package/templates/interactive-playground/src/mainview/demos/WindowDemo.ts +207 -0
  34. package/templates/interactive-playground/src/mainview/index.css +538 -0
  35. package/templates/interactive-playground/src/mainview/index.html +103 -0
  36. package/templates/interactive-playground/src/mainview/index.ts +238 -0
  37. package/templates/multitab-browser/README.md +34 -0
  38. package/templates/multitab-browser/bun.lock +224 -0
  39. package/templates/multitab-browser/electrobun.config.ts +32 -0
  40. package/templates/multitab-browser/package-lock.json +20 -0
  41. package/templates/multitab-browser/package.json +12 -0
  42. package/templates/multitab-browser/src/bun/index.ts +137 -0
  43. package/templates/multitab-browser/src/bun/tabManager.ts +200 -0
  44. package/templates/multitab-browser/src/bun/types/rpc.ts +78 -0
  45. package/templates/multitab-browser/src/mainview/index.css +487 -0
  46. package/templates/multitab-browser/src/mainview/index.html +94 -0
  47. package/templates/multitab-browser/src/mainview/index.ts +629 -0
  48. package/templates/photo-booth/electrobun.config.ts +28 -0
  49. package/templates/photo-booth/package.json +1 -1
  50. package/tests/bun.lock +14 -0
  51. package/tests/electrobun.config.ts +45 -0
  52. package/tests/package-lock.json +36 -0
  53. package/tests/package.json +13 -0
  54. package/tests/src/bun/index.ts +100 -0
  55. package/tests/src/bun/test-runner.ts +508 -0
  56. package/tests/src/mainview/index.html +110 -0
  57. package/tests/src/mainview/index.ts +458 -0
  58. package/tests/src/mainview/styles/main.css +451 -0
  59. package/tests/src/testviews/tray-test.html +57 -0
  60. package/tests/src/testviews/webview-mask.html +114 -0
  61. package/tests/src/testviews/webview-navigation.html +36 -0
  62. package/tests/src/testviews/window-create.html +17 -0
  63. package/tests/src/testviews/window-events.html +29 -0
  64. package/tests/src/testviews/window-focus.html +37 -0
  65. package/tests/src/webviewtag/index.ts +11 -0
  66. package/templates/hello-world/electrobun.config +0 -28
  67. package/templates/photo-booth/electrobun.config +0 -28
  68. package/templates/photo-booth/src/mainview/index_old.ts +0 -671
@@ -0,0 +1,26 @@
1
+ # Electrobun Interactive Playground
2
+
3
+ An interactive playground for exploring all Electrobun features through a clean, organized interface.
4
+
5
+ ## Features
6
+
7
+ - 🪟 **Window Management** - Create and manage multiple windows
8
+ - 📡 **RPC Communication** - Test bidirectional messaging
9
+ - 🎛️ **Menu Systems** - Application and context menus
10
+ - 🔔 **System Tray** - Tray icon management
11
+ - 🗂️ **File Operations** - File dialogs and system integration
12
+ - 🌐 **WebView Features** - Advanced webview capabilities
13
+ - ⚙️ **System Utilities** - Platform info and notifications
14
+ - 🔄 **Auto-Updates** - Update system demonstration
15
+
16
+ ## Getting Started
17
+
18
+ ```bash
19
+ npm install
20
+ npm run build:dev
21
+ npm start
22
+ ```
23
+
24
+ ## Development
25
+
26
+ This template demonstrates best practices for building Electrobun applications with modern UI patterns and comprehensive feature coverage.
@@ -0,0 +1,36 @@
1
+ export default {
2
+ app: {
3
+ name: "Electrobun Interactive Playground",
4
+ identifier: "dev.electrobun.interactive-playground",
5
+ version: "0.0.1",
6
+ },
7
+ build: {
8
+ bun: {
9
+ entrypoint: "src/bun/index.ts",
10
+ external: [],
11
+ },
12
+ views: {
13
+ mainview: {
14
+ entrypoint: "src/mainview/index.ts",
15
+ external: [],
16
+ },
17
+ },
18
+ copy: {
19
+ "src/mainview/index.html": "views/mainview/index.html",
20
+ "src/mainview/index.css": "views/mainview/index.css",
21
+ "assets/tray-icon.png": "views/assets/tray-icon.png",
22
+ },
23
+ mac: {
24
+ codesign: true,
25
+ notarize: false,
26
+ bundleCEF: true,
27
+ entitlements: {},
28
+ },
29
+ linux: {
30
+ bundleCEF: true,
31
+ },
32
+ win: {
33
+ bundleCEF: true,
34
+ },
35
+ },
36
+ };
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "electrobun-interactive-playground",
3
+ "version": "0.0.1",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "electrobun-interactive-playground",
9
+ "version": "0.0.1",
10
+ "dependencies": {
11
+ "electrobun": "file:../../"
12
+ }
13
+ },
14
+ "../..": {
15
+ "version": "0.0.19-beta.118",
16
+ "license": "MIT",
17
+ "dependencies": {
18
+ "@oneidentity/zstd-js": "^1.0.3",
19
+ "archiver": "^7.0.1",
20
+ "rpc-anywhere": "1.5.0",
21
+ "tar": "^6.2.1"
22
+ },
23
+ "bin": {
24
+ "electrobun": "bin/electrobun.cjs"
25
+ },
26
+ "devDependencies": {
27
+ "@types/archiver": "^6.0.3",
28
+ "@types/bun": "1.1.9"
29
+ }
30
+ },
31
+ "node_modules/electrobun": {
32
+ "resolved": "../..",
33
+ "link": true
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "electrobun-interactive-playground",
3
+ "version": "0.0.1",
4
+ "description": "Interactive playground for exploring Electrobun features",
5
+ "type": "module",
6
+ "scripts": {
7
+ "build:dev": "electrobun build",
8
+ "build:canary": "electrobun build --canary",
9
+ "start": "electrobun dev",
10
+ "start:canary": "electrobun dev --canary"
11
+ },
12
+ "dependencies": {
13
+ "electrobun": "latest"
14
+ }
15
+ }
@@ -0,0 +1,70 @@
1
+ import { Utils } from "electrobun/bun";
2
+ import { join } from "path";
3
+ import { homedir } from "os";
4
+
5
+ class FileManager {
6
+ async openFileDialog(options: {
7
+ multiple?: boolean;
8
+ fileTypes?: string[];
9
+ startingFolder?: string;
10
+ }) {
11
+ try {
12
+ const result = await Utils.openFileDialog({
13
+ startingFolder: options.startingFolder || join(homedir(), "Desktop"),
14
+ allowedFileTypes: options.fileTypes?.join(",") || "*",
15
+ canChooseFiles: true,
16
+ canChooseDirectory: false,
17
+ allowsMultipleSelection: options.multiple || false,
18
+ });
19
+
20
+ // Filter out empty strings
21
+ const filteredResult = result.filter(path => path.trim() !== "");
22
+ this.onFileSelected?.(filteredResult);
23
+
24
+ return filteredResult;
25
+ } catch (error) {
26
+ console.error("File dialog error:", error);
27
+ return [];
28
+ }
29
+ }
30
+
31
+ async moveToTrash(path: string) {
32
+ try {
33
+ await Utils.moveToTrash(path);
34
+ this.onSystemEvent?.({
35
+ type: 'file-trashed',
36
+ details: { path, success: true }
37
+ });
38
+ } catch (error) {
39
+ console.error("Move to trash error:", error);
40
+ this.onSystemEvent?.({
41
+ type: 'file-trashed',
42
+ details: { path, success: false, error: error.message }
43
+ });
44
+ throw error;
45
+ }
46
+ }
47
+
48
+ async showInFinder(path: string) {
49
+ try {
50
+ await Utils.showItemInFolder(path);
51
+ this.onSystemEvent?.({
52
+ type: 'show-in-finder',
53
+ details: { path, success: true }
54
+ });
55
+ } catch (error) {
56
+ console.error("Show in finder error:", error);
57
+ this.onSystemEvent?.({
58
+ type: 'show-in-finder',
59
+ details: { path, success: false, error: error.message }
60
+ });
61
+ throw error;
62
+ }
63
+ }
64
+
65
+ // Event callbacks
66
+ onFileSelected?: (paths: string[]) => void;
67
+ onSystemEvent?: (event: { type: string; details: any }) => void;
68
+ }
69
+
70
+ export const fileManager = new FileManager();
@@ -0,0 +1,139 @@
1
+ import { ApplicationMenu, ContextMenu, Tray } from "electrobun/bun";
2
+
3
+ class MenuManager {
4
+ private trays = new Map<number, Tray>();
5
+ private nextTrayId = 1;
6
+
7
+ constructor() {
8
+ this.setupApplicationMenu();
9
+ }
10
+
11
+ private setupApplicationMenu() {
12
+ ApplicationMenu.setApplicationMenu([
13
+ {
14
+ submenu: [
15
+ { label: "About", role: "about" },
16
+ { type: "separator" },
17
+ { label: "Quit", role: "quit", accelerator: "q" }
18
+ ],
19
+ },
20
+ {
21
+ label: "Edit",
22
+ submenu: [
23
+ { role: "undo" },
24
+ { role: "redo" },
25
+ { type: "separator" },
26
+ {
27
+ label: "Custom Demo Action",
28
+ action: "demo-action",
29
+ tooltip: "This is a demo menu item",
30
+ },
31
+ { type: "separator" },
32
+ { role: "cut" },
33
+ { role: "copy" },
34
+ { role: "paste" },
35
+ { role: "selectAll" },
36
+ ],
37
+ },
38
+ {
39
+ label: "View",
40
+ submenu: [
41
+ { role: "reload" },
42
+ { role: "forceReload" },
43
+ { role: "toggleDevTools" },
44
+ { type: "separator" },
45
+ { role: "resetZoom" },
46
+ { role: "zoomIn" },
47
+ { role: "zoomOut" },
48
+ { type: "separator" },
49
+ { role: "togglefullscreen" }
50
+ ],
51
+ },
52
+ {
53
+ label: "Window",
54
+ submenu: [
55
+ { role: "minimize" },
56
+ { role: "close" }
57
+ ],
58
+ }
59
+ ]);
60
+ }
61
+
62
+ async createTray(options: { title: string; image?: string }) {
63
+ const id = this.nextTrayId++;
64
+
65
+ const tray = new Tray({
66
+ title: options.title,
67
+ image: options.image || "views://assets/tray-icon.png",
68
+ template: true,
69
+ width: 32,
70
+ height: 32,
71
+ });
72
+
73
+ // Set up tray menu
74
+ tray.setMenu([
75
+ {
76
+ type: "normal",
77
+ label: "Show Playground",
78
+ action: "show-playground",
79
+ },
80
+ {
81
+ type: "separator",
82
+ },
83
+ {
84
+ type: "normal",
85
+ label: "Demo Action",
86
+ action: "demo-tray-action",
87
+ tooltip: "This is a demo tray action",
88
+ },
89
+ {
90
+ type: "normal",
91
+ label: "Quit",
92
+ action: "quit-app",
93
+ },
94
+ ]);
95
+
96
+ tray.on("tray-clicked", (e) => {
97
+ this.onTrayClicked?.(id, e.data.action);
98
+ });
99
+
100
+ this.trays.set(id, tray);
101
+ return { id };
102
+ }
103
+
104
+ async removeTray(id: number) {
105
+ const tray = this.trays.get(id);
106
+ if (tray) {
107
+ tray.remove();
108
+ this.trays.delete(id);
109
+ }
110
+ }
111
+
112
+ async showContextMenu(params: { x: number; y: number }) {
113
+ ContextMenu.showContextMenu([
114
+ { role: "undo" },
115
+ { role: "redo" },
116
+ { type: "separator" },
117
+ {
118
+ label: "Demo Context Action",
119
+ action: "demo-context-action",
120
+ tooltip: "This is a demo context menu item",
121
+ },
122
+ {
123
+ label: "Disabled Action",
124
+ action: "disabled-action",
125
+ enabled: false,
126
+ },
127
+ { type: "separator" },
128
+ { role: "cut" },
129
+ { role: "copy" },
130
+ { role: "paste" },
131
+ ]);
132
+ }
133
+
134
+ // Event callbacks
135
+ onTrayClicked?: (id: number, action: string) => void;
136
+ onMenuClicked?: (action: string) => void;
137
+ }
138
+
139
+ export const menuManager = new MenuManager();
@@ -0,0 +1,83 @@
1
+ class RPCTester {
2
+ async doMath(data: { a: number; b: number; operation: string }): Promise<number> {
3
+ const startTime = Date.now();
4
+ let result: number;
5
+
6
+ switch (data.operation) {
7
+ case 'add':
8
+ result = data.a + data.b;
9
+ break;
10
+ case 'subtract':
11
+ result = data.a - data.b;
12
+ break;
13
+ case 'multiply':
14
+ result = data.a * data.b;
15
+ break;
16
+ case 'divide':
17
+ if (data.b === 0) throw new Error("Division by zero");
18
+ result = data.a / data.b;
19
+ break;
20
+ case 'power':
21
+ result = Math.pow(data.a, data.b);
22
+ break;
23
+ default:
24
+ throw new Error(`Unknown operation: ${data.operation}`);
25
+ }
26
+
27
+ const duration = Date.now() - startTime;
28
+
29
+ // Don't send notification here - let the frontend handle timing and display
30
+ // to avoid duplicate entries
31
+
32
+ return result;
33
+ }
34
+
35
+ async echoBigData(data: string): Promise<string> {
36
+ const startTime = Date.now();
37
+
38
+ // Simulate some processing time
39
+ await new Promise(resolve => setTimeout(resolve, 100));
40
+
41
+ const response = `Echo: ${data.slice(0, 100)}... (${data.length} chars)`;
42
+ const duration = Date.now() - startTime;
43
+
44
+ // Don't send notification here - let the frontend handle timing and display
45
+ // to avoid duplicate entries
46
+
47
+ return response;
48
+ }
49
+
50
+ async performanceTest(messageSize: number, messageCount: number): Promise<{
51
+ totalTime: number;
52
+ averageTime: number;
53
+ messagesPerSecond: number;
54
+ }> {
55
+ const testData = "x".repeat(messageSize);
56
+ const results: number[] = [];
57
+
58
+ const startTime = Date.now();
59
+
60
+ for (let i = 0; i < messageCount; i++) {
61
+ const messageStart = Date.now();
62
+ await this.echoBigData(testData);
63
+ results.push(Date.now() - messageStart);
64
+ }
65
+
66
+ const totalTime = Date.now() - startTime;
67
+ const averageTime = results.reduce((a, b) => a + b, 0) / results.length;
68
+ const messagesPerSecond = (messageCount / totalTime) * 1000;
69
+
70
+ // Don't send notification here - let the frontend handle timing and display
71
+
72
+ return {
73
+ totalTime,
74
+ averageTime,
75
+ messagesPerSecond
76
+ };
77
+ }
78
+
79
+ // Event callbacks
80
+ onRpcTestResult?: (data: { operation: string; result: any; duration: number }) => void;
81
+ }
82
+
83
+ export const rpcTester = new RPCTester();
@@ -0,0 +1,72 @@
1
+ import Electrobun from "electrobun/bun";
2
+ import { platform, arch, release } from "os";
3
+
4
+ class SystemManager {
5
+ async getPlatformInfo() {
6
+ return {
7
+ platform: platform(),
8
+ arch: arch(),
9
+ version: release(),
10
+ electrobunVersion: "0.0.19-beta.118", // This should come from the actual API
11
+ };
12
+ }
13
+
14
+ async showNotification(options: {
15
+ title: string;
16
+ body: string;
17
+ icon?: string;
18
+ }) {
19
+ // Note: Notification API needs to be implemented in Electrobun
20
+ // For now, we'll simulate it
21
+ console.log("Notification:", options);
22
+
23
+ this.onSystemEvent?.({
24
+ type: 'notification-shown',
25
+ details: {
26
+ title: options.title,
27
+ body: options.body,
28
+ timestamp: new Date().toISOString()
29
+ }
30
+ });
31
+
32
+ // In a real implementation, this would use the native notification system
33
+ return Promise.resolve();
34
+ }
35
+
36
+ async getClipboardText(): Promise<string> {
37
+ // Note: Clipboard API needs to be implemented in Electrobun
38
+ // This is a placeholder
39
+ return "Clipboard API not yet implemented";
40
+ }
41
+
42
+ async setClipboardText(text: string): Promise<void> {
43
+ // Note: Clipboard API needs to be implemented in Electrobun
44
+ console.log("Setting clipboard text:", text);
45
+
46
+ this.onSystemEvent?.({
47
+ type: 'clipboard-set',
48
+ details: { text, timestamp: new Date().toISOString() }
49
+ });
50
+ }
51
+
52
+ async getScreenInfo() {
53
+ // Note: Screen API needs to be implemented in Electrobun
54
+ // This is a placeholder
55
+ return {
56
+ displays: [
57
+ {
58
+ id: 1,
59
+ width: 1920,
60
+ height: 1080,
61
+ scaleFactor: 1,
62
+ primary: true
63
+ }
64
+ ]
65
+ };
66
+ }
67
+
68
+ // Event callbacks
69
+ onSystemEvent?: (event: { type: string; details: any }) => void;
70
+ }
71
+
72
+ export const systemManager = new SystemManager();
@@ -0,0 +1,105 @@
1
+ import Electrobun from "electrobun/bun";
2
+
3
+ class UpdateManager {
4
+ async checkForUpdates() {
5
+ try {
6
+ const updateInfo = await Electrobun.Updater.checkForUpdate();
7
+
8
+ this.onUpdateStatus?.({
9
+ status: 'checked',
10
+ progress: 100
11
+ });
12
+
13
+ return {
14
+ updateAvailable: updateInfo.updateAvailable,
15
+ currentVersion: Electrobun.Updater.getLocalVersion?.() || "0.0.19-beta.118",
16
+ latestVersion: updateInfo.latestVersion,
17
+ };
18
+ } catch (error) {
19
+ console.error("Update check error:", error);
20
+
21
+ this.onUpdateStatus?.({
22
+ status: 'error',
23
+ progress: 0
24
+ });
25
+
26
+ return {
27
+ updateAvailable: false,
28
+ currentVersion: "0.0.19-beta.118",
29
+ error: error.message
30
+ };
31
+ }
32
+ }
33
+
34
+ async downloadUpdate() {
35
+ try {
36
+ this.onUpdateStatus?.({
37
+ status: 'downloading',
38
+ progress: 0
39
+ });
40
+
41
+ // Simulate download progress
42
+ for (let i = 0; i <= 100; i += 10) {
43
+ await new Promise(resolve => setTimeout(resolve, 200));
44
+ this.onUpdateStatus?.({
45
+ status: 'downloading',
46
+ progress: i
47
+ });
48
+ }
49
+
50
+ await Electrobun.Updater.downloadUpdate();
51
+
52
+ this.onUpdateStatus?.({
53
+ status: 'downloaded',
54
+ progress: 100
55
+ });
56
+
57
+ return { success: true };
58
+ } catch (error) {
59
+ console.error("Update download error:", error);
60
+
61
+ this.onUpdateStatus?.({
62
+ status: 'error',
63
+ progress: 0
64
+ });
65
+
66
+ return { success: false, error: error.message };
67
+ }
68
+ }
69
+
70
+ async applyUpdate() {
71
+ try {
72
+ this.onUpdateStatus?.({
73
+ status: 'applying',
74
+ progress: 50
75
+ });
76
+
77
+ await Electrobun.Updater.applyUpdate();
78
+
79
+ this.onUpdateStatus?.({
80
+ status: 'applied',
81
+ progress: 100
82
+ });
83
+
84
+ return { success: true };
85
+ } catch (error) {
86
+ console.error("Update apply error:", error);
87
+
88
+ this.onUpdateStatus?.({
89
+ status: 'error',
90
+ progress: 0
91
+ });
92
+
93
+ return { success: false, error: error.message };
94
+ }
95
+ }
96
+
97
+ getUpdateInfo() {
98
+ return Electrobun.Updater.updateInfo?.() || null;
99
+ }
100
+
101
+ // Event callbacks
102
+ onUpdateStatus?: (data: { status: string; progress?: number }) => void;
103
+ }
104
+
105
+ export const updateManager = new UpdateManager();
@@ -0,0 +1,90 @@
1
+ import { BrowserWindow } from "electrobun/bun";
2
+
3
+ class WindowManager {
4
+ private windows = new Map<number, BrowserWindow>();
5
+ private nextId = 1;
6
+
7
+ async createWindow(options: {
8
+ width: number;
9
+ height: number;
10
+ x: number;
11
+ y: number;
12
+ frameless?: boolean;
13
+ transparent?: boolean;
14
+ alwaysOnTop?: boolean;
15
+ }) {
16
+ const id = this.nextId++;
17
+
18
+ const window = new BrowserWindow({
19
+ title: `Demo Window ${id}`,
20
+ url: "views://mainview/index.html",
21
+ renderer: "cef",
22
+ frame: {
23
+ width: options.width,
24
+ height: options.height,
25
+ x: options.x,
26
+ y: options.y,
27
+ },
28
+ titleBarStyle: options.frameless ? "hiddenInset" : "default",
29
+ // For completely frameless, we need to set styleMask
30
+ styleMask: options.frameless ? {
31
+ Borderless: true,
32
+ Titled: false,
33
+ Closable: true,
34
+ Miniaturizable: true,
35
+ Resizable: true,
36
+ } : undefined,
37
+ });
38
+
39
+ this.windows.set(id, window);
40
+
41
+ // Listen for window events
42
+ window.on("close", () => {
43
+ this.windows.delete(id);
44
+ this.onWindowClosed?.(id);
45
+ });
46
+
47
+ window.on("resize", (event) => {
48
+ this.onWindowEvent?.({ type: 'resize', id, data: event.data });
49
+ });
50
+
51
+ window.on("move", (event) => {
52
+ this.onWindowEvent?.({ type: 'move', id, data: event.data });
53
+ });
54
+
55
+ this.onWindowCreated?.(id, `Demo Window ${id}`);
56
+
57
+ return { id };
58
+ }
59
+
60
+ async closeWindow(id: number) {
61
+ const window = this.windows.get(id);
62
+ if (window) {
63
+ window.close();
64
+ this.windows.delete(id);
65
+ }
66
+ }
67
+
68
+ async focusWindow(id: number) {
69
+ const window = this.windows.get(id);
70
+ if (window) {
71
+ window.focus();
72
+ this.onWindowFocused?.(id);
73
+ }
74
+ }
75
+
76
+ async getWindowList() {
77
+ return Array.from(this.windows.entries()).map(([id, window]) => ({
78
+ id,
79
+ title: window.getTitle() || `Window ${id}`,
80
+ }));
81
+ }
82
+
83
+ // Event callbacks
84
+ onWindowCreated?: (id: number, title: string) => void;
85
+ onWindowClosed?: (id: number) => void;
86
+ onWindowFocused?: (id: number) => void;
87
+ onWindowEvent?: (event: { type: string; id: number; data: any }) => void;
88
+ }
89
+
90
+ export const windowManager = new WindowManager();