electrobun 0.5.0-beta.0 → 0.7.0-beta.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/{templates/multitab-browser/bun.lock → bun.lock} +20 -13
- package/dist/api/bun/core/BrowserView.ts +5 -1
- package/dist/api/bun/events/webviewEvents.ts +8 -0
- package/dist/api/bun/proc/native.ts +95 -21
- package/package.json +14 -16
- package/BETA_RELEASE.md +0 -67
- package/BUILD.md +0 -90
- package/LICENSE +0 -21
- package/README.md +0 -102
- package/debug.js +0 -5
- package/templates/hello-world/README.md +0 -57
- package/templates/hello-world/bun.lock +0 -225
- package/templates/hello-world/electrobun.config.ts +0 -28
- package/templates/hello-world/package.json +0 -16
- package/templates/hello-world/src/bun/index.ts +0 -15
- package/templates/hello-world/src/mainview/index.css +0 -124
- package/templates/hello-world/src/mainview/index.html +0 -46
- package/templates/hello-world/src/mainview/index.ts +0 -1
- package/templates/interactive-playground/README.md +0 -26
- package/templates/interactive-playground/assets/tray-icon.png +0 -0
- package/templates/interactive-playground/electrobun.config.ts +0 -36
- package/templates/interactive-playground/package-lock.json +0 -1112
- package/templates/interactive-playground/package.json +0 -15
- package/templates/interactive-playground/src/bun/demos/files.ts +0 -70
- package/templates/interactive-playground/src/bun/demos/menus.ts +0 -139
- package/templates/interactive-playground/src/bun/demos/rpc.ts +0 -83
- package/templates/interactive-playground/src/bun/demos/system.ts +0 -72
- package/templates/interactive-playground/src/bun/demos/updates.ts +0 -105
- package/templates/interactive-playground/src/bun/demos/windows.ts +0 -90
- package/templates/interactive-playground/src/bun/index.ts +0 -124
- package/templates/interactive-playground/src/bun/types/rpc.ts +0 -109
- package/templates/interactive-playground/src/mainview/components/EventLog.ts +0 -107
- package/templates/interactive-playground/src/mainview/components/Sidebar.ts +0 -65
- package/templates/interactive-playground/src/mainview/components/Toast.ts +0 -57
- package/templates/interactive-playground/src/mainview/demos/FileDemo.ts +0 -211
- package/templates/interactive-playground/src/mainview/demos/MenuDemo.ts +0 -102
- package/templates/interactive-playground/src/mainview/demos/RPCDemo.ts +0 -229
- package/templates/interactive-playground/src/mainview/demos/TrayDemo.ts +0 -132
- package/templates/interactive-playground/src/mainview/demos/WebViewDemo.ts +0 -465
- package/templates/interactive-playground/src/mainview/demos/WindowDemo.ts +0 -207
- package/templates/interactive-playground/src/mainview/index.css +0 -538
- package/templates/interactive-playground/src/mainview/index.html +0 -103
- package/templates/interactive-playground/src/mainview/index.ts +0 -238
- package/templates/multitab-browser/README.md +0 -34
- package/templates/multitab-browser/electrobun.config.ts +0 -32
- package/templates/multitab-browser/package-lock.json +0 -20
- package/templates/multitab-browser/package.json +0 -12
- package/templates/multitab-browser/src/bun/index.ts +0 -144
- package/templates/multitab-browser/src/bun/tabManager.ts +0 -200
- package/templates/multitab-browser/src/bun/types/rpc.ts +0 -78
- package/templates/multitab-browser/src/mainview/index.css +0 -487
- package/templates/multitab-browser/src/mainview/index.html +0 -94
- package/templates/multitab-browser/src/mainview/index.ts +0 -634
- package/templates/photo-booth/README.md +0 -108
- package/templates/photo-booth/bun.lock +0 -239
- package/templates/photo-booth/electrobun.config.ts +0 -32
- package/templates/photo-booth/package.json +0 -17
- package/templates/photo-booth/src/bun/index.ts +0 -92
- package/templates/photo-booth/src/mainview/index.css +0 -465
- package/templates/photo-booth/src/mainview/index.html +0 -124
- package/templates/photo-booth/src/mainview/index.ts +0 -499
- package/test-new-window-events.ts +0 -26
- package/test-new-window.html +0 -75
- package/test-npm-install.sh +0 -34
- package/tests/bun.lock +0 -14
- package/tests/electrobun.config.ts +0 -45
- package/tests/package-lock.json +0 -36
- package/tests/package.json +0 -13
- package/tests/src/bun/index.ts +0 -100
- package/tests/src/bun/test-runner.ts +0 -508
- package/tests/src/mainview/index.html +0 -110
- package/tests/src/mainview/index.ts +0 -458
- package/tests/src/mainview/styles/main.css +0 -451
- package/tests/src/testviews/tray-test.html +0 -57
- package/tests/src/testviews/webview-mask.html +0 -114
- package/tests/src/testviews/webview-navigation.html +0 -36
- package/tests/src/testviews/window-create.html +0 -17
- package/tests/src/testviews/window-events.html +0 -29
- package/tests/src/testviews/window-focus.html +0 -37
- package/tests/src/webviewtag/index.ts +0 -11
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
import Electrobun, { Electroview } from "electrobun/view";
|
|
2
|
-
import { type PlaygroundRPC } from "../bun/types/rpc";
|
|
3
|
-
|
|
4
|
-
// Import components
|
|
5
|
-
import { Sidebar } from "./components/Sidebar";
|
|
6
|
-
import { EventLog } from "./components/EventLog";
|
|
7
|
-
import { Toast } from "./components/Toast";
|
|
8
|
-
|
|
9
|
-
// Import demos
|
|
10
|
-
import { WindowDemo } from "./demos/WindowDemo";
|
|
11
|
-
import { RPCDemo } from "./demos/RPCDemo";
|
|
12
|
-
import { MenuDemo } from "./demos/MenuDemo";
|
|
13
|
-
import { TrayDemo } from "./demos/TrayDemo";
|
|
14
|
-
import { FileDemo } from "./demos/FileDemo";
|
|
15
|
-
import { WebViewDemo } from "./demos/WebViewDemo";
|
|
16
|
-
|
|
17
|
-
class InteractivePlayground {
|
|
18
|
-
private electrobun: any;
|
|
19
|
-
private sidebar: Sidebar;
|
|
20
|
-
private eventLog: EventLog;
|
|
21
|
-
private demos: Map<string, any> = new Map();
|
|
22
|
-
|
|
23
|
-
constructor() {
|
|
24
|
-
console.log("🎮 Initializing Interactive Playground...");
|
|
25
|
-
|
|
26
|
-
// Set up RPC
|
|
27
|
-
const rpc = Electroview.defineRPC<PlaygroundRPC>({
|
|
28
|
-
maxRequestTime: 10000,
|
|
29
|
-
handlers: {
|
|
30
|
-
requests: {},
|
|
31
|
-
messages: {
|
|
32
|
-
// Window events
|
|
33
|
-
windowCreated: (data) => {
|
|
34
|
-
this.eventLog.addEntry('info', `Window created: ${data.title}`, data);
|
|
35
|
-
this.demos.get('windows')?.onWindowCreated(data);
|
|
36
|
-
Toast.success(`Window created: ${data.title}`);
|
|
37
|
-
},
|
|
38
|
-
|
|
39
|
-
windowClosed: (data) => {
|
|
40
|
-
this.eventLog.addEntry('info', `Window closed: ${data.id}`, data);
|
|
41
|
-
this.demos.get('windows')?.onWindowClosed(data);
|
|
42
|
-
Toast.info(`Window ${data.id} closed`);
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
windowFocused: (data) => {
|
|
46
|
-
this.eventLog.addEntry('info', `Window focused: ${data.id}`, data);
|
|
47
|
-
this.demos.get('windows')?.onWindowFocused(data);
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
// Tray events
|
|
51
|
-
trayClicked: (data) => {
|
|
52
|
-
this.eventLog.addEntry('info', `Tray clicked: ${data.action}`, data);
|
|
53
|
-
this.demos.get('tray')?.onTrayClicked?.(data);
|
|
54
|
-
Toast.info(`Tray action: ${data.action}`);
|
|
55
|
-
},
|
|
56
|
-
|
|
57
|
-
// Menu events
|
|
58
|
-
menuClicked: (data) => {
|
|
59
|
-
this.eventLog.addEntry('info', `Menu clicked: ${data.action}`, data);
|
|
60
|
-
this.demos.get('menus')?.onMenuClicked?.(data);
|
|
61
|
-
Toast.info(`Menu action: ${data.action}`);
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
// File events
|
|
65
|
-
fileSelected: (data) => {
|
|
66
|
-
this.eventLog.addEntry('info', `Files selected: ${data.paths.length} files`, data);
|
|
67
|
-
this.demos.get('files')?.onFileSelected(data);
|
|
68
|
-
Toast.success(`Selected ${data.paths.length} file(s)`);
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
// RPC test results
|
|
72
|
-
rpcTestResult: (data) => {
|
|
73
|
-
this.eventLog.addEntry('info', `RPC test completed: ${data.operation}`, data);
|
|
74
|
-
this.demos.get('rpc')?.onRpcTestResult(data);
|
|
75
|
-
},
|
|
76
|
-
|
|
77
|
-
// System events
|
|
78
|
-
systemEvent: (data) => {
|
|
79
|
-
this.eventLog.addEntry('info', `System event: ${data.type}`, data);
|
|
80
|
-
Toast.info(`System: ${data.type}`);
|
|
81
|
-
},
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// Log messages
|
|
85
|
-
logMessage: (data) => {
|
|
86
|
-
this.eventLog.addEntry(data.level, data.message);
|
|
87
|
-
if (data.level === 'error') {
|
|
88
|
-
Toast.error(data.message);
|
|
89
|
-
} else if (data.level === 'warn') {
|
|
90
|
-
Toast.warning(data.message);
|
|
91
|
-
} else {
|
|
92
|
-
Toast.info(data.message);
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// Create Electroview instance
|
|
100
|
-
this.electrobun = new Electrobun.Electroview({ rpc });
|
|
101
|
-
|
|
102
|
-
// Initialize components
|
|
103
|
-
this.sidebar = new Sidebar();
|
|
104
|
-
this.eventLog = new EventLog();
|
|
105
|
-
|
|
106
|
-
// Initialize demos
|
|
107
|
-
this.initializeDemos();
|
|
108
|
-
|
|
109
|
-
// Set up event listeners
|
|
110
|
-
this.setupEventListeners();
|
|
111
|
-
|
|
112
|
-
// Load initial demo
|
|
113
|
-
this.loadDemo('windows');
|
|
114
|
-
|
|
115
|
-
console.log("✅ Interactive Playground initialized");
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private initializeDemos() {
|
|
119
|
-
this.demos.set('windows', new WindowDemo());
|
|
120
|
-
this.demos.set('rpc', new RPCDemo());
|
|
121
|
-
this.demos.set('menus', new MenuDemo());
|
|
122
|
-
this.demos.set('tray', new TrayDemo());
|
|
123
|
-
this.demos.set('files', new FileDemo());
|
|
124
|
-
this.demos.set('webviews', new WebViewDemo());
|
|
125
|
-
// Add more demos as they're implemented
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
private setupEventListeners() {
|
|
129
|
-
this.sidebar.onDemoChangeCallback((demo) => {
|
|
130
|
-
this.loadDemo(demo);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// Global error handling
|
|
134
|
-
window.addEventListener('error', (event) => {
|
|
135
|
-
this.eventLog.addEntry('error', event.message, {
|
|
136
|
-
filename: event.filename,
|
|
137
|
-
lineno: event.lineno,
|
|
138
|
-
colno: event.colno
|
|
139
|
-
});
|
|
140
|
-
Toast.error(`JavaScript Error: ${event.message}`);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// Unhandled promise rejections
|
|
144
|
-
window.addEventListener('unhandledrejection', (event) => {
|
|
145
|
-
this.eventLog.addEntry('error', `Unhandled Promise Rejection: ${event.reason}`, event.reason);
|
|
146
|
-
Toast.error('Unhandled Promise Rejection');
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
private loadDemo(demoName: string) {
|
|
151
|
-
const content = document.getElementById('demo-content');
|
|
152
|
-
if (!content) return;
|
|
153
|
-
|
|
154
|
-
// CRITICAL: Cleanup any existing webviews before switching demos
|
|
155
|
-
// This prevents crashes when CEF tries to clean up stale webview references
|
|
156
|
-
this.cleanupWebviews();
|
|
157
|
-
|
|
158
|
-
const demo = this.demos.get(demoName);
|
|
159
|
-
if (!demo) {
|
|
160
|
-
content.innerHTML = this.renderPlaceholderDemo(demoName);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Render demo content
|
|
165
|
-
content.innerHTML = demo.render();
|
|
166
|
-
|
|
167
|
-
// Initialize demo with RPC
|
|
168
|
-
if (demo.initialize) {
|
|
169
|
-
demo.initialize(this.electrobun.rpc);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
this.eventLog.addEntry('info', `Loaded demo: ${demoName}`);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
private cleanupWebviews() {
|
|
176
|
-
// Find all webview elements and properly remove them
|
|
177
|
-
const webviews = document.querySelectorAll('electrobun-webview');
|
|
178
|
-
|
|
179
|
-
webviews.forEach((webview: any) => {
|
|
180
|
-
try {
|
|
181
|
-
// Call the native remove method if it exists
|
|
182
|
-
if (typeof webview.remove === 'function') {
|
|
183
|
-
webview.remove();
|
|
184
|
-
}
|
|
185
|
-
} catch (error) {
|
|
186
|
-
console.warn('Error during webview cleanup:', error);
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
// Additional safety: clear any webview-related event listeners
|
|
191
|
-
// This helps prevent memory leaks and stale references
|
|
192
|
-
webviews.forEach((webview: any) => {
|
|
193
|
-
try {
|
|
194
|
-
if (typeof webview.removeAllEventListeners === 'function') {
|
|
195
|
-
webview.removeAllEventListeners();
|
|
196
|
-
}
|
|
197
|
-
} catch (error) {
|
|
198
|
-
// Silently ignore if method doesn't exist
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
private renderPlaceholderDemo(demoName: string): string {
|
|
204
|
-
const demoInfo: Record<string, { icon: string; title: string; description: string }> = {
|
|
205
|
-
menus: { icon: '🎛️', title: 'Menu Systems', description: 'Application and context menus' },
|
|
206
|
-
tray: { icon: '🔔', title: 'System Tray', description: 'Tray icon management' },
|
|
207
|
-
files: { icon: '🗂️', title: 'File Operations', description: 'File dialogs and system integration' },
|
|
208
|
-
webviews: { icon: '🌐', title: 'WebView Features', description: 'Advanced webview capabilities' }
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
const info = demoInfo[demoName] || { icon: '🔧', title: 'Coming Soon', description: 'This demo is under development' };
|
|
212
|
-
|
|
213
|
-
return `
|
|
214
|
-
<div class="demo-section">
|
|
215
|
-
<div class="demo-header">
|
|
216
|
-
<span class="demo-icon">${info.icon}</span>
|
|
217
|
-
<div>
|
|
218
|
-
<h2 class="demo-title">${info.title}</h2>
|
|
219
|
-
<p class="demo-description">${info.description}</p>
|
|
220
|
-
</div>
|
|
221
|
-
</div>
|
|
222
|
-
|
|
223
|
-
<div class="demo-controls">
|
|
224
|
-
<div style="text-align: center; padding: 3rem; color: #718096;">
|
|
225
|
-
<h3>Coming Soon!</h3>
|
|
226
|
-
<p>This demo is currently under development.</p>
|
|
227
|
-
<p>Check back soon for interactive examples of ${info.title.toLowerCase()}.</p>
|
|
228
|
-
</div>
|
|
229
|
-
</div>
|
|
230
|
-
</div>
|
|
231
|
-
`;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Initialize the playground when DOM is ready
|
|
236
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
237
|
-
new InteractivePlayground();
|
|
238
|
-
});
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# Multitab Browser
|
|
2
|
-
|
|
3
|
-
A demonstration of building a multi-tab browser using Electrobun framework.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- Multiple browser tabs with independent webviews
|
|
8
|
-
- Navigation controls (back, forward, refresh, home)
|
|
9
|
-
- URL bar with navigation
|
|
10
|
-
- Tab management (new, close, switch)
|
|
11
|
-
- Bookmark functionality
|
|
12
|
-
- History tracking
|
|
13
|
-
|
|
14
|
-
## Running the Demo
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
# Install dependencies
|
|
18
|
-
bun install
|
|
19
|
-
|
|
20
|
-
# Run in development mode
|
|
21
|
-
bun run dev
|
|
22
|
-
|
|
23
|
-
# Build for production
|
|
24
|
-
bun run build
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Architecture
|
|
28
|
-
|
|
29
|
-
This template demonstrates:
|
|
30
|
-
- Using BrowserView for embedded web content
|
|
31
|
-
- RPC communication between main process and renderer
|
|
32
|
-
- Tab state management
|
|
33
|
-
- Keyboard shortcuts
|
|
34
|
-
- URL handling and navigation
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
app: {
|
|
3
|
-
name: "multitab-browser",
|
|
4
|
-
identifier: "multitab-browser.electrobun.dev",
|
|
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
|
-
},
|
|
22
|
-
mac: {
|
|
23
|
-
bundleCEF: true,
|
|
24
|
-
},
|
|
25
|
-
linux: {
|
|
26
|
-
bundleCEF: true,
|
|
27
|
-
},
|
|
28
|
-
win: {
|
|
29
|
-
bundleCEF: true,
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "multitab-browser",
|
|
3
|
-
"version": "0.0.1",
|
|
4
|
-
"lockfileVersion": 3,
|
|
5
|
-
"requires": true,
|
|
6
|
-
"packages": {
|
|
7
|
-
"": {
|
|
8
|
-
"name": "multitab-browser",
|
|
9
|
-
"version": "0.0.1",
|
|
10
|
-
"dependencies": {
|
|
11
|
-
"electrobun": "file:../"
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
"..": {},
|
|
15
|
-
"node_modules/electrobun": {
|
|
16
|
-
"resolved": "..",
|
|
17
|
-
"link": true
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "multitab-browser",
|
|
3
|
-
"version": "0.0.1",
|
|
4
|
-
"description": "A multi-tab browser demo for Electrobun",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"start": "electrobun build && electrobun dev"
|
|
8
|
-
},
|
|
9
|
-
"dependencies": {
|
|
10
|
-
"electrobun": "latest"
|
|
11
|
-
}
|
|
12
|
-
}
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import Electrobun, { BrowserWindow, BrowserView } from "electrobun/bun";
|
|
2
|
-
|
|
3
|
-
console.log("🌐 Multitab Browser starting...");
|
|
4
|
-
|
|
5
|
-
// Simplified tab management - demo without real webviews
|
|
6
|
-
const tabs = new Map();
|
|
7
|
-
let nextTabId = 1;
|
|
8
|
-
let mainRPC: any = null; // Will be set after window creation
|
|
9
|
-
|
|
10
|
-
// Set up RPC using the correct API pattern from interactive-playground
|
|
11
|
-
const rpc = BrowserView.defineRPC({
|
|
12
|
-
maxRequestTime: 10000,
|
|
13
|
-
handlers: {
|
|
14
|
-
requests: {
|
|
15
|
-
createTab: async ({ url }: { url?: string }) => {
|
|
16
|
-
const id = `tab-${nextTabId++}`;
|
|
17
|
-
|
|
18
|
-
const tab = {
|
|
19
|
-
id,
|
|
20
|
-
title: "New Tab",
|
|
21
|
-
url: url || "https://electrobun.dev",
|
|
22
|
-
canGoBack: false,
|
|
23
|
-
canGoForward: false,
|
|
24
|
-
isLoading: false,
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
tabs.set(id, tab);
|
|
28
|
-
|
|
29
|
-
// Simulate getting the title after a delay
|
|
30
|
-
setTimeout(() => {
|
|
31
|
-
if (mainRPC) {
|
|
32
|
-
tab.title = new URL(tab.url).hostname || "New Tab";
|
|
33
|
-
mainRPC.send("tabUpdated", tab);
|
|
34
|
-
}
|
|
35
|
-
}, 500);
|
|
36
|
-
|
|
37
|
-
return tab;
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
closeTab: async ({ id }: { id: string }) => {
|
|
41
|
-
tabs.delete(id);
|
|
42
|
-
return;
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
activateTab: async ({ tabId }: { tabId: string }) => {
|
|
46
|
-
// Just return the tab info - the iframe switching happens in the frontend
|
|
47
|
-
return tabs.get(tabId);
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
navigateTo: async ({ tabId, url }: { tabId: string; url: string }) => {
|
|
51
|
-
const tab = tabs.get(tabId);
|
|
52
|
-
|
|
53
|
-
if (tab) {
|
|
54
|
-
// Process URL - add https if needed, or search
|
|
55
|
-
let processedUrl = url;
|
|
56
|
-
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
57
|
-
if (url.includes(".") && !url.includes(" ")) {
|
|
58
|
-
processedUrl = `https://${url}`;
|
|
59
|
-
} else {
|
|
60
|
-
processedUrl = `https://www.google.com/search?q=${encodeURIComponent(url)}`;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
tab.url = processedUrl;
|
|
65
|
-
tab.isLoading = false;
|
|
66
|
-
|
|
67
|
-
// Simulate title update
|
|
68
|
-
setTimeout(() => {
|
|
69
|
-
if (mainRPC && tab) {
|
|
70
|
-
try {
|
|
71
|
-
tab.title = new URL(processedUrl).hostname || "New Tab";
|
|
72
|
-
} catch {
|
|
73
|
-
tab.title = processedUrl;
|
|
74
|
-
}
|
|
75
|
-
mainRPC.send("tabUpdated", tab);
|
|
76
|
-
}
|
|
77
|
-
}, 500);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return tab;
|
|
81
|
-
},
|
|
82
|
-
|
|
83
|
-
goBack: async ({ tabId }: { tabId: string }) => {
|
|
84
|
-
// In a real implementation, we'd track history
|
|
85
|
-
console.log("Go back for tab:", tabId);
|
|
86
|
-
return tabs.get(tabId);
|
|
87
|
-
},
|
|
88
|
-
|
|
89
|
-
goForward: async ({ tabId }: { tabId: string }) => {
|
|
90
|
-
// In a real implementation, we'd track history
|
|
91
|
-
console.log("Go forward for tab:", tabId);
|
|
92
|
-
return tabs.get(tabId);
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
reload: async ({ tabId }: { tabId: string }) => {
|
|
96
|
-
const tab = tabs.get(tabId);
|
|
97
|
-
if (tab) {
|
|
98
|
-
tab.isLoading = true;
|
|
99
|
-
if (mainRPC) {
|
|
100
|
-
mainRPC.send("tabUpdated", tab);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
setTimeout(() => {
|
|
104
|
-
tab.isLoading = false;
|
|
105
|
-
if (mainRPC) {
|
|
106
|
-
mainRPC.send("tabUpdated", tab);
|
|
107
|
-
}
|
|
108
|
-
}, 1000);
|
|
109
|
-
}
|
|
110
|
-
return tab;
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
messages: {
|
|
114
|
-
"*": (messageName: string, payload: any) => {
|
|
115
|
-
console.log(`📨 Browser message: ${messageName}`, payload);
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
// Create main browser window with RPC
|
|
122
|
-
const mainWindow = new BrowserWindow({
|
|
123
|
-
title: "Multitab Browser",
|
|
124
|
-
url: "views://mainview/index.html",
|
|
125
|
-
frame: {
|
|
126
|
-
width: 1400,
|
|
127
|
-
height: 900,
|
|
128
|
-
x: 100,
|
|
129
|
-
y: 100,
|
|
130
|
-
},
|
|
131
|
-
rpc,
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// Store reference to mainWindow RPC for sending messages
|
|
135
|
-
mainRPC = mainWindow.webview.rpc;
|
|
136
|
-
|
|
137
|
-
// Listen for window close event and exit the app
|
|
138
|
-
// For this browser app, we want to exit when the main window is closed
|
|
139
|
-
mainWindow.on("close", () => {
|
|
140
|
-
console.log("🚪 Main window closed - exiting app");
|
|
141
|
-
process.exit(0);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
console.log("✅ Multitab Browser initialized");
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import { BrowserView } from "electrobun/bun";
|
|
2
|
-
import { type Tab, type Bookmark } from "./types/rpc";
|
|
3
|
-
|
|
4
|
-
export class TabManager {
|
|
5
|
-
private tabs: Map<string, Tab> = new Map();
|
|
6
|
-
private webviews: Map<string, BrowserView> = new Map();
|
|
7
|
-
private bookmarks: Map<string, Bookmark> = new Map();
|
|
8
|
-
private nextTabId = 1;
|
|
9
|
-
|
|
10
|
-
public onTabUpdate?: (tab: Tab) => void;
|
|
11
|
-
public onLoadingStateChange?: (tabId: string, isLoading: boolean) => void;
|
|
12
|
-
|
|
13
|
-
constructor() {
|
|
14
|
-
// Load bookmarks from storage if available
|
|
15
|
-
this.loadBookmarks();
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async createTab(url: string): Promise<Tab> {
|
|
19
|
-
const id = `tab-${this.nextTabId++}`;
|
|
20
|
-
|
|
21
|
-
// Create a BrowserView for this tab
|
|
22
|
-
const webview = new BrowserView({
|
|
23
|
-
url,
|
|
24
|
-
frame: {
|
|
25
|
-
x: 0,
|
|
26
|
-
y: 100, // Leave space for tab bar and navigation
|
|
27
|
-
width: 1400,
|
|
28
|
-
height: 800,
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
// Set up webview event handlers
|
|
33
|
-
webview.on("page-title-updated", (event) => {
|
|
34
|
-
const tab = this.tabs.get(id);
|
|
35
|
-
if (tab) {
|
|
36
|
-
tab.title = event.data.title || "New Tab";
|
|
37
|
-
this.onTabUpdate?.(tab);
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
webview.on("did-start-loading", () => {
|
|
42
|
-
const tab = this.tabs.get(id);
|
|
43
|
-
if (tab) {
|
|
44
|
-
tab.isLoading = true;
|
|
45
|
-
this.onLoadingStateChange?.(id, true);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
webview.on("did-stop-loading", () => {
|
|
50
|
-
const tab = this.tabs.get(id);
|
|
51
|
-
if (tab) {
|
|
52
|
-
tab.isLoading = false;
|
|
53
|
-
this.onLoadingStateChange?.(id, false);
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
webview.on("did-navigate", (event) => {
|
|
58
|
-
const tab = this.tabs.get(id);
|
|
59
|
-
if (tab && event.data.url) {
|
|
60
|
-
tab.url = event.data.url;
|
|
61
|
-
// Update navigation state
|
|
62
|
-
this.updateNavigationState(id);
|
|
63
|
-
this.onTabUpdate?.(tab);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
const tab: Tab = {
|
|
68
|
-
id,
|
|
69
|
-
title: "New Tab",
|
|
70
|
-
url,
|
|
71
|
-
canGoBack: false,
|
|
72
|
-
canGoForward: false,
|
|
73
|
-
isLoading: true,
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
this.tabs.set(id, tab);
|
|
77
|
-
this.webviews.set(id, webview);
|
|
78
|
-
|
|
79
|
-
return tab;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async closeTab(id: string): Promise<void> {
|
|
83
|
-
const webview = this.webviews.get(id);
|
|
84
|
-
if (webview) {
|
|
85
|
-
webview.destroy();
|
|
86
|
-
this.webviews.delete(id);
|
|
87
|
-
}
|
|
88
|
-
this.tabs.delete(id);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async navigateTo(tabId: string, url: string): Promise<void> {
|
|
92
|
-
const webview = this.webviews.get(tabId);
|
|
93
|
-
if (webview) {
|
|
94
|
-
// Ensure URL has protocol
|
|
95
|
-
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
96
|
-
// Check if it looks like a domain
|
|
97
|
-
if (url.includes(".") && !url.includes(" ")) {
|
|
98
|
-
url = `https://${url}`;
|
|
99
|
-
} else {
|
|
100
|
-
// Treat as search query
|
|
101
|
-
url = `https://www.google.com/search?q=${encodeURIComponent(url)}`;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
await webview.loadURL(url);
|
|
106
|
-
const tab = this.tabs.get(tabId);
|
|
107
|
-
if (tab) {
|
|
108
|
-
tab.url = url;
|
|
109
|
-
tab.isLoading = true;
|
|
110
|
-
this.onTabUpdate?.(tab);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async goBack(tabId: string): Promise<void> {
|
|
116
|
-
const webview = this.webviews.get(tabId);
|
|
117
|
-
if (webview) {
|
|
118
|
-
await webview.goBack();
|
|
119
|
-
this.updateNavigationState(tabId);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async goForward(tabId: string): Promise<void> {
|
|
124
|
-
const webview = this.webviews.get(tabId);
|
|
125
|
-
if (webview) {
|
|
126
|
-
await webview.goForward();
|
|
127
|
-
this.updateNavigationState(tabId);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async reload(tabId: string): Promise<void> {
|
|
132
|
-
const webview = this.webviews.get(tabId);
|
|
133
|
-
if (webview) {
|
|
134
|
-
await webview.reload();
|
|
135
|
-
const tab = this.tabs.get(tabId);
|
|
136
|
-
if (tab) {
|
|
137
|
-
tab.isLoading = true;
|
|
138
|
-
this.onTabUpdate?.(tab);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async getTabInfo(tabId: string): Promise<Tab | undefined> {
|
|
144
|
-
return this.tabs.get(tabId);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
getAllTabs(): Tab[] {
|
|
148
|
-
return Array.from(this.tabs.values());
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
private async updateNavigationState(tabId: string): Promise<void> {
|
|
152
|
-
const webview = this.webviews.get(tabId);
|
|
153
|
-
const tab = this.tabs.get(tabId);
|
|
154
|
-
|
|
155
|
-
if (webview && tab) {
|
|
156
|
-
// Note: These methods might not be available in current Electrobun API
|
|
157
|
-
// This is a placeholder for navigation state management
|
|
158
|
-
tab.canGoBack = false; // Would need webview.canGoBack()
|
|
159
|
-
tab.canGoForward = false; // Would need webview.canGoForward()
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Bookmark management
|
|
164
|
-
addBookmark(title: string, url: string): Bookmark {
|
|
165
|
-
const id = `bookmark-${Date.now()}`;
|
|
166
|
-
const bookmark: Bookmark = {
|
|
167
|
-
id,
|
|
168
|
-
title,
|
|
169
|
-
url,
|
|
170
|
-
createdAt: Date.now(),
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
this.bookmarks.set(id, bookmark);
|
|
174
|
-
this.saveBookmarks();
|
|
175
|
-
return bookmark;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
getBookmarks(): Bookmark[] {
|
|
179
|
-
return Array.from(this.bookmarks.values()).sort((a, b) => b.createdAt - a.createdAt);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
removeBookmark(id: string): void {
|
|
183
|
-
this.bookmarks.delete(id);
|
|
184
|
-
this.saveBookmarks();
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
private loadBookmarks(): void {
|
|
188
|
-
// In a real app, load from persistent storage
|
|
189
|
-
// For demo, we'll start with some default bookmarks
|
|
190
|
-
this.addBookmark("Google", "https://www.google.com");
|
|
191
|
-
this.addBookmark("GitHub", "https://github.com");
|
|
192
|
-
this.addBookmark("Electrobun", "https://electrobun.dev");
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
private saveBookmarks(): void {
|
|
196
|
-
// In a real app, save to persistent storage
|
|
197
|
-
// For demo, we'll just log
|
|
198
|
-
console.log("Bookmarks saved:", this.getBookmarks());
|
|
199
|
-
}
|
|
200
|
-
}
|