electrobun 0.0.19-beta.97 → 0.1.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.md +1 -1
- package/dist/api/browser/webviewtag.ts +54 -2
- package/dist/api/bun/ElectrobunConfig.ts +171 -0
- package/dist/api/bun/core/BrowserWindow.ts +4 -0
- package/dist/api/bun/core/Tray.ts +14 -0
- package/dist/api/bun/core/Updater.ts +4 -3
- package/dist/api/bun/index.ts +2 -0
- package/dist/api/bun/proc/native.ts +107 -5
- package/dist/main.js +5 -4
- package/package.json +4 -2
- package/src/cli/index.ts +565 -148
- package/templates/hello-world/bun.lock +164 -2
- package/templates/hello-world/electrobun.config.ts +28 -0
- package/templates/hello-world/src/bun/index.ts +2 -2
- package/templates/hello-world/src/mainview/index.html +5 -6
- package/templates/hello-world/src/mainview/index.ts +1 -5
- package/templates/interactive-playground/README.md +26 -0
- package/templates/interactive-playground/assets/tray-icon.png +0 -0
- package/templates/interactive-playground/electrobun.config.ts +36 -0
- package/templates/interactive-playground/package-lock.json +36 -0
- package/templates/interactive-playground/package.json +15 -0
- package/templates/interactive-playground/src/bun/demos/files.ts +70 -0
- package/templates/interactive-playground/src/bun/demos/menus.ts +139 -0
- package/templates/interactive-playground/src/bun/demos/rpc.ts +83 -0
- package/templates/interactive-playground/src/bun/demos/system.ts +72 -0
- package/templates/interactive-playground/src/bun/demos/updates.ts +105 -0
- package/templates/interactive-playground/src/bun/demos/windows.ts +90 -0
- package/templates/interactive-playground/src/bun/index.ts +124 -0
- package/templates/interactive-playground/src/bun/types/rpc.ts +109 -0
- package/templates/interactive-playground/src/mainview/components/EventLog.ts +107 -0
- package/templates/interactive-playground/src/mainview/components/Sidebar.ts +65 -0
- package/templates/interactive-playground/src/mainview/components/Toast.ts +57 -0
- package/templates/interactive-playground/src/mainview/demos/FileDemo.ts +211 -0
- package/templates/interactive-playground/src/mainview/demos/MenuDemo.ts +102 -0
- package/templates/interactive-playground/src/mainview/demos/RPCDemo.ts +229 -0
- package/templates/interactive-playground/src/mainview/demos/TrayDemo.ts +132 -0
- package/templates/interactive-playground/src/mainview/demos/WebViewDemo.ts +411 -0
- package/templates/interactive-playground/src/mainview/demos/WindowDemo.ts +207 -0
- package/templates/interactive-playground/src/mainview/index.css +538 -0
- package/templates/interactive-playground/src/mainview/index.html +103 -0
- package/templates/interactive-playground/src/mainview/index.ts +238 -0
- package/templates/multitab-browser/README.md +34 -0
- package/templates/multitab-browser/bun.lock +224 -0
- package/templates/multitab-browser/electrobun.config.ts +32 -0
- package/templates/multitab-browser/package-lock.json +20 -0
- package/templates/multitab-browser/package.json +12 -0
- package/templates/multitab-browser/src/bun/index.ts +144 -0
- package/templates/multitab-browser/src/bun/tabManager.ts +200 -0
- package/templates/multitab-browser/src/bun/types/rpc.ts +78 -0
- package/templates/multitab-browser/src/mainview/index.css +487 -0
- package/templates/multitab-browser/src/mainview/index.html +94 -0
- package/templates/multitab-browser/src/mainview/index.ts +634 -0
- package/templates/photo-booth/README.md +108 -0
- package/templates/photo-booth/bun.lock +239 -0
- package/templates/photo-booth/electrobun.config.ts +28 -0
- package/templates/photo-booth/package.json +16 -0
- package/templates/photo-booth/src/bun/index.ts +92 -0
- package/templates/photo-booth/src/mainview/index.css +465 -0
- package/templates/photo-booth/src/mainview/index.html +124 -0
- package/templates/photo-booth/src/mainview/index.ts +499 -0
- package/tests/bun.lock +14 -0
- package/tests/electrobun.config.ts +45 -0
- package/tests/package-lock.json +36 -0
- package/tests/package.json +13 -0
- package/tests/src/bun/index.ts +100 -0
- package/tests/src/bun/test-runner.ts +508 -0
- package/tests/src/mainview/index.html +110 -0
- package/tests/src/mainview/index.ts +458 -0
- package/tests/src/mainview/styles/main.css +451 -0
- package/tests/src/testviews/tray-test.html +57 -0
- package/tests/src/testviews/webview-mask.html +114 -0
- package/tests/src/testviews/webview-navigation.html +36 -0
- package/tests/src/testviews/window-create.html +17 -0
- package/tests/src/testviews/window-events.html +29 -0
- package/tests/src/testviews/window-focus.html +37 -0
- package/tests/src/webviewtag/index.ts +11 -0
- package/templates/hello-world/electrobun.config +0 -18
|
@@ -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();
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import Electrobun, { BrowserWindow, BrowserView } from "electrobun/bun";
|
|
2
|
+
import { type PlaygroundRPC } from "./types/rpc";
|
|
3
|
+
|
|
4
|
+
// Import demo modules
|
|
5
|
+
import { windowManager } from "./demos/windows";
|
|
6
|
+
import { menuManager } from "./demos/menus";
|
|
7
|
+
import { fileManager } from "./demos/files";
|
|
8
|
+
import { rpcTester } from "./demos/rpc";
|
|
9
|
+
|
|
10
|
+
console.log("🚀 Electrobun Interactive Playground starting...");
|
|
11
|
+
|
|
12
|
+
// Set up RPC communication
|
|
13
|
+
const rpc = BrowserView.defineRPC<PlaygroundRPC>({
|
|
14
|
+
maxRequestTime: 10000,
|
|
15
|
+
handlers: {
|
|
16
|
+
requests: {
|
|
17
|
+
// Window Management
|
|
18
|
+
createWindow: windowManager.createWindow.bind(windowManager),
|
|
19
|
+
closeWindow: windowManager.closeWindow.bind(windowManager),
|
|
20
|
+
focusWindow: windowManager.focusWindow.bind(windowManager),
|
|
21
|
+
getWindowList: windowManager.getWindowList.bind(windowManager),
|
|
22
|
+
|
|
23
|
+
// RPC Testing
|
|
24
|
+
doMath: rpcTester.doMath.bind(rpcTester),
|
|
25
|
+
echoBigData: rpcTester.echoBigData.bind(rpcTester),
|
|
26
|
+
|
|
27
|
+
// Menu Operations
|
|
28
|
+
createTray: menuManager.createTray.bind(menuManager),
|
|
29
|
+
removeTray: menuManager.removeTray.bind(menuManager),
|
|
30
|
+
showContextMenu: menuManager.showContextMenu.bind(menuManager),
|
|
31
|
+
|
|
32
|
+
// File Operations
|
|
33
|
+
openFileDialog: fileManager.openFileDialog.bind(fileManager),
|
|
34
|
+
moveToTrash: fileManager.moveToTrash.bind(fileManager),
|
|
35
|
+
showInFinder: fileManager.showInFinder.bind(fileManager),
|
|
36
|
+
|
|
37
|
+
// WebView Operations (placeholder)
|
|
38
|
+
createWebView: async (url: string) => ({ id: 1 }),
|
|
39
|
+
executeJSInWebView: async (params: { id: number; script: string }) => null,
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
},
|
|
43
|
+
messages: {
|
|
44
|
+
"*": (messageName, payload) => {
|
|
45
|
+
console.log(`📨 Message received: ${messageName}`, payload);
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Create main playground window
|
|
52
|
+
const mainWindow = new BrowserWindow({
|
|
53
|
+
title: "Electrobun Interactive Playground",
|
|
54
|
+
url: "views://mainview/index.html",
|
|
55
|
+
renderer: "cef",
|
|
56
|
+
frame: {
|
|
57
|
+
width: 1400,
|
|
58
|
+
height: 900,
|
|
59
|
+
x: 100,
|
|
60
|
+
y: 100,
|
|
61
|
+
},
|
|
62
|
+
titleBarStyle: "default",
|
|
63
|
+
rpc,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Set up event forwarding from demo modules to the UI
|
|
67
|
+
windowManager.onWindowCreated = (id, title) => {
|
|
68
|
+
rpc.send.windowCreated({ id, title });
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
windowManager.onWindowClosed = (id) => {
|
|
72
|
+
rpc.send.windowClosed({ id });
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
windowManager.onWindowFocused = (id) => {
|
|
76
|
+
rpc.send.windowFocused({ id });
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
menuManager.onTrayClicked = (id, action) => {
|
|
80
|
+
rpc.send.trayClicked({ id, action });
|
|
81
|
+
console.log(`🔔 Tray ${id} clicked: ${action}`);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
menuManager.onMenuClicked = (action) => {
|
|
85
|
+
rpc.send.menuClicked({ action });
|
|
86
|
+
console.log(`🎛️ Menu clicked: ${action}`);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
fileManager.onFileSelected = (paths) => {
|
|
90
|
+
rpc.send.fileSelected({ paths });
|
|
91
|
+
console.log(`📁 Files selected:`, paths);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
fileManager.onSystemEvent = (event) => {
|
|
95
|
+
rpc.send.systemEvent(event);
|
|
96
|
+
console.log(`⚙️ System event:`, event);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
rpcTester.onRpcTestResult = (data) => {
|
|
100
|
+
rpc.send.rpcTestResult(data);
|
|
101
|
+
console.log(`📡 RPC test result:`, data);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
// Listen for global events
|
|
107
|
+
Electrobun.events.on("application-menu-clicked", (e) => {
|
|
108
|
+
menuManager.onMenuClicked?.(e.data.action);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
Electrobun.events.on("context-menu-clicked", (e) => {
|
|
112
|
+
menuManager.onMenuClicked?.(e.data.action);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Send initial status
|
|
116
|
+
mainWindow.webview.on("dom-ready", () => {
|
|
117
|
+
console.log("✅ Main window DOM ready");
|
|
118
|
+
rpc.send.logMessage({
|
|
119
|
+
level: 'info',
|
|
120
|
+
message: 'Electrobun Interactive Playground loaded successfully!'
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
console.log("🎮 Playground initialized successfully");
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { type RPCSchema } from "electrobun";
|
|
2
|
+
|
|
3
|
+
export type PlaygroundRPC = {
|
|
4
|
+
bun: RPCSchema<{
|
|
5
|
+
requests: {
|
|
6
|
+
// Window Management
|
|
7
|
+
createWindow: {
|
|
8
|
+
params: {
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
frameless?: boolean;
|
|
14
|
+
transparent?: boolean;
|
|
15
|
+
alwaysOnTop?: boolean;
|
|
16
|
+
};
|
|
17
|
+
response: { id: number };
|
|
18
|
+
};
|
|
19
|
+
closeWindow: {
|
|
20
|
+
params: number; // window id
|
|
21
|
+
response: void;
|
|
22
|
+
};
|
|
23
|
+
focusWindow: {
|
|
24
|
+
params: number; // window id
|
|
25
|
+
response: void;
|
|
26
|
+
};
|
|
27
|
+
getWindowList: {
|
|
28
|
+
params: void;
|
|
29
|
+
response: Array<{ id: number; title: string }>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// RPC Testing
|
|
33
|
+
doMath: {
|
|
34
|
+
params: { a: number; b: number; operation: string };
|
|
35
|
+
response: number;
|
|
36
|
+
};
|
|
37
|
+
echoBigData: {
|
|
38
|
+
params: string;
|
|
39
|
+
response: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Menu Operations
|
|
43
|
+
createTray: {
|
|
44
|
+
params: { title: string; image?: string };
|
|
45
|
+
response: { id: number };
|
|
46
|
+
};
|
|
47
|
+
removeTray: {
|
|
48
|
+
params: number; // tray id
|
|
49
|
+
response: void;
|
|
50
|
+
};
|
|
51
|
+
showContextMenu: {
|
|
52
|
+
params: { x: number; y: number };
|
|
53
|
+
response: void;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// File Operations
|
|
57
|
+
openFileDialog: {
|
|
58
|
+
params: {
|
|
59
|
+
multiple?: boolean;
|
|
60
|
+
fileTypes?: string[];
|
|
61
|
+
startingFolder?: string;
|
|
62
|
+
};
|
|
63
|
+
response: string[];
|
|
64
|
+
};
|
|
65
|
+
moveToTrash: {
|
|
66
|
+
params: string; // file path
|
|
67
|
+
response: void;
|
|
68
|
+
};
|
|
69
|
+
showInFinder: {
|
|
70
|
+
params: string; // file path
|
|
71
|
+
response: void;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// WebView Operations
|
|
75
|
+
createWebView: {
|
|
76
|
+
params: string; // url
|
|
77
|
+
response: { id: number };
|
|
78
|
+
};
|
|
79
|
+
executeJSInWebView: {
|
|
80
|
+
params: { id: number; script: string };
|
|
81
|
+
response: any;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
};
|
|
86
|
+
messages: {};
|
|
87
|
+
}>;
|
|
88
|
+
|
|
89
|
+
webview: RPCSchema<{
|
|
90
|
+
requests: {};
|
|
91
|
+
messages: {
|
|
92
|
+
// Messages from bun to webview
|
|
93
|
+
windowCreated: { id: number; title: string };
|
|
94
|
+
windowClosed: { id: number };
|
|
95
|
+
windowFocused: { id: number };
|
|
96
|
+
|
|
97
|
+
trayClicked: { id: number; action: string };
|
|
98
|
+
menuClicked: { action: string };
|
|
99
|
+
|
|
100
|
+
fileSelected: { paths: string[] };
|
|
101
|
+
|
|
102
|
+
rpcTestResult: { operation: string; result: any; duration: number };
|
|
103
|
+
|
|
104
|
+
systemEvent: { type: string; details: any };
|
|
105
|
+
|
|
106
|
+
logMessage: { level: 'info' | 'warn' | 'error'; message: string };
|
|
107
|
+
};
|
|
108
|
+
}>;
|
|
109
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
interface LogEntry {
|
|
2
|
+
timestamp: Date;
|
|
3
|
+
level: 'info' | 'warn' | 'error';
|
|
4
|
+
message: string;
|
|
5
|
+
data?: any;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class EventLog {
|
|
9
|
+
private entries: LogEntry[] = [];
|
|
10
|
+
private isOpen: boolean = false;
|
|
11
|
+
private filters: Set<string> = new Set(['info', 'warn', 'error']);
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this.initializeEventListeners();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private initializeEventListeners() {
|
|
18
|
+
const toggle = document.getElementById('event-log-toggle');
|
|
19
|
+
const clearBtn = document.getElementById('clear-log');
|
|
20
|
+
const filterCheckboxes = document.querySelectorAll('.event-log-filters input[type="checkbox"]');
|
|
21
|
+
|
|
22
|
+
toggle?.addEventListener('click', () => {
|
|
23
|
+
this.toggle();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
clearBtn?.addEventListener('click', () => {
|
|
27
|
+
this.clear();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
filterCheckboxes.forEach(checkbox => {
|
|
31
|
+
checkbox.addEventListener('change', (e) => {
|
|
32
|
+
const target = e.target as HTMLInputElement;
|
|
33
|
+
const level = target.getAttribute('data-level');
|
|
34
|
+
if (level) {
|
|
35
|
+
if (target.checked) {
|
|
36
|
+
this.filters.add(level);
|
|
37
|
+
} else {
|
|
38
|
+
this.filters.delete(level);
|
|
39
|
+
}
|
|
40
|
+
this.render();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
toggle() {
|
|
47
|
+
this.isOpen = !this.isOpen;
|
|
48
|
+
const eventLog = document.getElementById('event-log');
|
|
49
|
+
eventLog?.classList.toggle('open', this.isOpen);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
addEntry(level: 'info' | 'warn' | 'error', message: string, data?: any) {
|
|
53
|
+
const entry: LogEntry = {
|
|
54
|
+
timestamp: new Date(),
|
|
55
|
+
level,
|
|
56
|
+
message,
|
|
57
|
+
data
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
this.entries.unshift(entry); // Add to beginning
|
|
61
|
+
|
|
62
|
+
// Keep only last 100 entries
|
|
63
|
+
if (this.entries.length > 100) {
|
|
64
|
+
this.entries = this.entries.slice(0, 100);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.render();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
clear() {
|
|
71
|
+
this.entries = [];
|
|
72
|
+
this.render();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private render() {
|
|
76
|
+
const container = document.getElementById('event-log-list');
|
|
77
|
+
if (!container) return;
|
|
78
|
+
|
|
79
|
+
const filteredEntries = this.entries.filter(entry => this.filters.has(entry.level));
|
|
80
|
+
|
|
81
|
+
container.innerHTML = filteredEntries.map(entry => `
|
|
82
|
+
<div class="event-entry ${entry.level}">
|
|
83
|
+
<div class="event-time">${this.formatTime(entry.timestamp)}</div>
|
|
84
|
+
<div class="event-message">${this.escapeHtml(entry.message)}</div>
|
|
85
|
+
${entry.data ? `<pre class="event-data">${this.escapeHtml(JSON.stringify(entry.data, null, 2))}</pre>` : ''}
|
|
86
|
+
</div>
|
|
87
|
+
`).join('');
|
|
88
|
+
|
|
89
|
+
// Auto-scroll to top for new entries
|
|
90
|
+
container.scrollTop = 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private formatTime(date: Date): string {
|
|
94
|
+
return date.toLocaleTimeString('en-US', {
|
|
95
|
+
hour12: false,
|
|
96
|
+
hour: '2-digit',
|
|
97
|
+
minute: '2-digit',
|
|
98
|
+
second: '2-digit'
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private escapeHtml(text: string): string {
|
|
103
|
+
const div = document.createElement('div');
|
|
104
|
+
div.textContent = text;
|
|
105
|
+
return div.innerHTML;
|
|
106
|
+
}
|
|
107
|
+
}
|