electrobun 0.4.1 → 0.6.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/proc/native.ts +84 -16
- 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,109 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,107 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
export class Sidebar {
|
|
2
|
-
private currentDemo: string = 'windows';
|
|
3
|
-
private onDemoChange?: (demo: string) => void;
|
|
4
|
-
|
|
5
|
-
constructor() {
|
|
6
|
-
this.initializeEventListeners();
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
private initializeEventListeners() {
|
|
10
|
-
const navItems = document.querySelectorAll('.nav-item');
|
|
11
|
-
|
|
12
|
-
navItems.forEach(item => {
|
|
13
|
-
item.addEventListener('click', () => {
|
|
14
|
-
const demo = item.getAttribute('data-demo');
|
|
15
|
-
if (demo && demo !== this.currentDemo) {
|
|
16
|
-
this.setActiveDemo(demo);
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
// Search functionality
|
|
22
|
-
const searchInput = document.getElementById('search') as HTMLInputElement;
|
|
23
|
-
searchInput?.addEventListener('input', (e) => {
|
|
24
|
-
this.filterDemos((e.target as HTMLInputElement).value);
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
setActiveDemo(demo: string) {
|
|
29
|
-
// Remove active class from all items
|
|
30
|
-
document.querySelectorAll('.nav-item').forEach(item => {
|
|
31
|
-
item.classList.remove('active');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// Add active class to selected item
|
|
35
|
-
const selectedItem = document.querySelector(`[data-demo="${demo}"]`);
|
|
36
|
-
selectedItem?.classList.add('active');
|
|
37
|
-
|
|
38
|
-
this.currentDemo = demo;
|
|
39
|
-
this.onDemoChange?.(demo);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
private filterDemos(query: string) {
|
|
43
|
-
const lowerQuery = query.toLowerCase();
|
|
44
|
-
const navItems = document.querySelectorAll('.nav-item');
|
|
45
|
-
|
|
46
|
-
navItems.forEach(item => {
|
|
47
|
-
const label = item.querySelector('.nav-label')?.textContent?.toLowerCase() || '';
|
|
48
|
-
const demo = item.getAttribute('data-demo')?.toLowerCase() || '';
|
|
49
|
-
|
|
50
|
-
if (label.includes(lowerQuery) || demo.includes(lowerQuery)) {
|
|
51
|
-
(item as HTMLElement).style.display = 'flex';
|
|
52
|
-
} else {
|
|
53
|
-
(item as HTMLElement).style.display = 'none';
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
onDemoChangeCallback(callback: (demo: string) => void) {
|
|
59
|
-
this.onDemoChange = callback;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
getCurrentDemo() {
|
|
63
|
-
return this.currentDemo;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
export type ToastType = 'info' | 'success' | 'warning' | 'error';
|
|
2
|
-
|
|
3
|
-
export class Toast {
|
|
4
|
-
static show(message: string, type: ToastType = 'info', duration: number = 3000) {
|
|
5
|
-
const container = document.getElementById('toast-container');
|
|
6
|
-
if (!container) return;
|
|
7
|
-
|
|
8
|
-
const toast = document.createElement('div');
|
|
9
|
-
toast.className = `toast ${type}`;
|
|
10
|
-
toast.innerHTML = `
|
|
11
|
-
<div class="toast-content">
|
|
12
|
-
<div class="toast-message">${this.escapeHtml(message)}</div>
|
|
13
|
-
</div>
|
|
14
|
-
`;
|
|
15
|
-
|
|
16
|
-
container.appendChild(toast);
|
|
17
|
-
|
|
18
|
-
// Auto-remove after duration
|
|
19
|
-
setTimeout(() => {
|
|
20
|
-
this.remove(toast);
|
|
21
|
-
}, duration);
|
|
22
|
-
|
|
23
|
-
// Allow manual dismissal
|
|
24
|
-
toast.addEventListener('click', () => {
|
|
25
|
-
this.remove(toast);
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
private static remove(toast: HTMLElement) {
|
|
30
|
-
toast.style.animation = 'slideOut 0.3s ease forwards';
|
|
31
|
-
setTimeout(() => {
|
|
32
|
-
toast.remove();
|
|
33
|
-
}, 300);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
private static escapeHtml(text: string): string {
|
|
37
|
-
const div = document.createElement('div');
|
|
38
|
-
div.textContent = text;
|
|
39
|
-
return div.innerHTML;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
static success(message: string, duration?: number) {
|
|
43
|
-
this.show(message, 'success', duration);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
static error(message: string, duration?: number) {
|
|
47
|
-
this.show(message, 'error', duration);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
static warning(message: string, duration?: number) {
|
|
51
|
-
this.show(message, 'warning', duration);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
static info(message: string, duration?: number) {
|
|
55
|
-
this.show(message, 'info', duration);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
export class FileDemo {
|
|
2
|
-
private selectedFiles: string[] = [];
|
|
3
|
-
|
|
4
|
-
render() {
|
|
5
|
-
return `
|
|
6
|
-
<div class="demo-section">
|
|
7
|
-
<div class="demo-header">
|
|
8
|
-
<span class="demo-icon">🗂️</span>
|
|
9
|
-
<div>
|
|
10
|
-
<h2 class="demo-title">File Operations</h2>
|
|
11
|
-
<p class="demo-description">Test file dialogs, file system operations, and drag & drop functionality</p>
|
|
12
|
-
</div>
|
|
13
|
-
</div>
|
|
14
|
-
|
|
15
|
-
<div class="demo-controls">
|
|
16
|
-
<h3>File Dialog</h3>
|
|
17
|
-
<div class="control-group">
|
|
18
|
-
<label class="control-checkbox">
|
|
19
|
-
<input type="checkbox" id="file-multiple"> Allow multiple selection
|
|
20
|
-
</label>
|
|
21
|
-
|
|
22
|
-
<label class="control-label">File Types:</label>
|
|
23
|
-
<select id="file-types" class="control-input" style="width: 150px;">
|
|
24
|
-
<option value="">All Files (*)</option>
|
|
25
|
-
<option value="txt,md">Text Files</option>
|
|
26
|
-
<option value="png,jpg,jpeg,gif">Images</option>
|
|
27
|
-
<option value="pdf">PDF Files</option>
|
|
28
|
-
<option value="json,js,ts">Code Files</option>
|
|
29
|
-
</select>
|
|
30
|
-
|
|
31
|
-
<button class="btn btn-primary" id="open-file-dialog">Open File Dialog</button>
|
|
32
|
-
</div>
|
|
33
|
-
|
|
34
|
-
<h3>File Operations</h3>
|
|
35
|
-
<div class="control-group">
|
|
36
|
-
<button class="btn btn-secondary" id="move-to-trash" disabled>Move Selected to Trash</button>
|
|
37
|
-
<button class="btn btn-secondary" id="show-in-finder" disabled>Show in Finder</button>
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
<h3>Drag & Drop Test</h3>
|
|
41
|
-
<div class="drop-zone" id="drop-zone" style="border: 2px dashed #cbd5e0; border-radius: 0.5rem; padding: 2rem; text-align: center; color: #718096; margin: 1rem 0; background: #f7fafc;">
|
|
42
|
-
<div>📁 Drag and drop files here</div>
|
|
43
|
-
<div style="font-size: 0.875rem; margin-top: 0.5rem;">Or click "Open File Dialog" above</div>
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
46
|
-
|
|
47
|
-
<div class="demo-results">
|
|
48
|
-
<div class="results-header">Selected Files (<span id="file-count">0</span>):</div>
|
|
49
|
-
<div id="file-list" class="file-list">
|
|
50
|
-
<div class="no-files" style="text-align: center; color: #718096; padding: 2rem;">
|
|
51
|
-
No files selected yet.
|
|
52
|
-
</div>
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
55
|
-
</div>
|
|
56
|
-
`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
initialize(rpc: any) {
|
|
60
|
-
const openDialogBtn = document.getElementById('open-file-dialog');
|
|
61
|
-
const moveToTrashBtn = document.getElementById('move-to-trash');
|
|
62
|
-
const showInFinderBtn = document.getElementById('show-in-finder');
|
|
63
|
-
const dropZone = document.getElementById('drop-zone');
|
|
64
|
-
|
|
65
|
-
openDialogBtn?.addEventListener('click', async () => {
|
|
66
|
-
const multiple = (document.getElementById('file-multiple') as HTMLInputElement).checked;
|
|
67
|
-
const fileTypes = (document.getElementById('file-types') as HTMLSelectElement).value;
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
const files = await rpc.request.openFileDialog({
|
|
71
|
-
multiple,
|
|
72
|
-
fileTypes: fileTypes ? fileTypes.split(',') : undefined
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
this.selectedFiles = files;
|
|
76
|
-
this.updateFileList();
|
|
77
|
-
} catch (error) {
|
|
78
|
-
console.error('Error opening file dialog:', error);
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
moveToTrashBtn?.addEventListener('click', async () => {
|
|
83
|
-
if (this.selectedFiles.length === 0) return;
|
|
84
|
-
|
|
85
|
-
const confirmed = confirm(`Are you sure you want to move ${this.selectedFiles.length} file(s) to trash?`);
|
|
86
|
-
if (!confirmed) return;
|
|
87
|
-
|
|
88
|
-
for (const file of this.selectedFiles) {
|
|
89
|
-
try {
|
|
90
|
-
await rpc.request.moveToTrash(file);
|
|
91
|
-
} catch (error) {
|
|
92
|
-
console.error(`Error moving ${file} to trash:`, error);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
this.selectedFiles = [];
|
|
97
|
-
this.updateFileList();
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
showInFinderBtn?.addEventListener('click', async () => {
|
|
101
|
-
if (this.selectedFiles.length === 0) return;
|
|
102
|
-
|
|
103
|
-
// Show the first selected file in finder
|
|
104
|
-
try {
|
|
105
|
-
await rpc.request.showInFinder(this.selectedFiles[0]);
|
|
106
|
-
} catch (error) {
|
|
107
|
-
console.error('Error showing in finder:', error);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// Set up drag and drop
|
|
112
|
-
if (dropZone) {
|
|
113
|
-
this.setupDragAndDrop(dropZone);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
private setupDragAndDrop(dropZone: HTMLElement) {
|
|
118
|
-
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
|
119
|
-
dropZone.addEventListener(eventName, (e) => {
|
|
120
|
-
e.preventDefault();
|
|
121
|
-
e.stopPropagation();
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
['dragenter', 'dragover'].forEach(eventName => {
|
|
126
|
-
dropZone.addEventListener(eventName, () => {
|
|
127
|
-
dropZone.style.background = '#ebf8ff';
|
|
128
|
-
dropZone.style.borderColor = '#4299e1';
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
['dragleave', 'drop'].forEach(eventName => {
|
|
133
|
-
dropZone.addEventListener(eventName, () => {
|
|
134
|
-
dropZone.style.background = '#f7fafc';
|
|
135
|
-
dropZone.style.borderColor = '#cbd5e0';
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
dropZone.addEventListener('drop', (e) => {
|
|
140
|
-
const dt = e.dataTransfer;
|
|
141
|
-
if (!dt) return;
|
|
142
|
-
|
|
143
|
-
const files = Array.from(dt.files).map(file => file.path || file.name);
|
|
144
|
-
this.selectedFiles = files;
|
|
145
|
-
this.updateFileList();
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
private updateFileList() {
|
|
150
|
-
const container = document.getElementById('file-list');
|
|
151
|
-
const count = document.getElementById('file-count');
|
|
152
|
-
const moveToTrashBtn = document.getElementById('move-to-trash');
|
|
153
|
-
const showInFinderBtn = document.getElementById('show-in-finder');
|
|
154
|
-
|
|
155
|
-
if (!container || !count) return;
|
|
156
|
-
|
|
157
|
-
count.textContent = this.selectedFiles.length.toString();
|
|
158
|
-
|
|
159
|
-
// Enable/disable action buttons
|
|
160
|
-
const hasFiles = this.selectedFiles.length > 0;
|
|
161
|
-
moveToTrashBtn?.toggleAttribute('disabled', !hasFiles);
|
|
162
|
-
showInFinderBtn?.toggleAttribute('disabled', !hasFiles);
|
|
163
|
-
|
|
164
|
-
if (this.selectedFiles.length === 0) {
|
|
165
|
-
container.innerHTML = `
|
|
166
|
-
<div class="no-files" style="text-align: center; color: #718096; padding: 2rem;">
|
|
167
|
-
No files selected yet.
|
|
168
|
-
</div>
|
|
169
|
-
`;
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
container.innerHTML = this.selectedFiles.map((file, index) => `
|
|
174
|
-
<div class="file-item" style="background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 0.5rem; padding: 1rem; margin-bottom: 0.5rem;">
|
|
175
|
-
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
176
|
-
<div style="flex: 1; min-width: 0;">
|
|
177
|
-
<div style="font-weight: 500; color: #2d3748; margin-bottom: 0.25rem;">
|
|
178
|
-
${this.getFileName(file)}
|
|
179
|
-
</div>
|
|
180
|
-
<div style="color: #718096; font-size: 0.875rem; word-break: break-all;">
|
|
181
|
-
${file}
|
|
182
|
-
</div>
|
|
183
|
-
</div>
|
|
184
|
-
<div style="margin-left: 1rem;">
|
|
185
|
-
<button class="btn btn-small btn-secondary remove-file" data-index="${index}">Remove</button>
|
|
186
|
-
</div>
|
|
187
|
-
</div>
|
|
188
|
-
</div>
|
|
189
|
-
`).join('');
|
|
190
|
-
|
|
191
|
-
// Add remove button listeners
|
|
192
|
-
const removeButtons = document.querySelectorAll('.remove-file');
|
|
193
|
-
removeButtons.forEach(btn => {
|
|
194
|
-
btn.addEventListener('click', (e) => {
|
|
195
|
-
const index = parseInt((e.target as HTMLElement).getAttribute('data-index') || '0');
|
|
196
|
-
this.selectedFiles.splice(index, 1);
|
|
197
|
-
this.updateFileList();
|
|
198
|
-
});
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
private getFileName(path: string): string {
|
|
203
|
-
return path.split('/').pop() || path.split('\\').pop() || path;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Handle events from the backend
|
|
207
|
-
onFileSelected(data: { paths: string[] }) {
|
|
208
|
-
this.selectedFiles = data.paths;
|
|
209
|
-
this.updateFileList();
|
|
210
|
-
}
|
|
211
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
export class MenuDemo {
|
|
2
|
-
private rpc: any;
|
|
3
|
-
|
|
4
|
-
render() {
|
|
5
|
-
return `
|
|
6
|
-
<div class="demo-section">
|
|
7
|
-
<div class="demo-header">
|
|
8
|
-
<span class="demo-icon">🎛️</span>
|
|
9
|
-
<div>
|
|
10
|
-
<h2 class="demo-title">Menu Systems</h2>
|
|
11
|
-
<p class="demo-description">Test application menus and context menus</p>
|
|
12
|
-
</div>
|
|
13
|
-
</div>
|
|
14
|
-
|
|
15
|
-
<div class="demo-controls">
|
|
16
|
-
<h3>Application Menu</h3>
|
|
17
|
-
<div class="control-group">
|
|
18
|
-
<div style="padding: 1rem; background: #f7fafc; border-radius: 0.5rem;">
|
|
19
|
-
<p style="color: #4a5568; margin-bottom: 0.5rem;">
|
|
20
|
-
The application menu is automatically set up. Look at the menu bar at the top of your screen.
|
|
21
|
-
</p>
|
|
22
|
-
<p style="color: #718096; font-size: 0.875rem;">
|
|
23
|
-
Try: Edit → Custom Menu Item 🚀
|
|
24
|
-
</p>
|
|
25
|
-
</div>
|
|
26
|
-
</div>
|
|
27
|
-
|
|
28
|
-
<h3>Context Menu</h3>
|
|
29
|
-
<div class="control-group">
|
|
30
|
-
<button class="btn btn-primary" id="show-context-menu">Show Context Menu</button>
|
|
31
|
-
<span style="color: #718096; font-size: 0.875rem;">Click to show a context menu at cursor position</span>
|
|
32
|
-
</div>
|
|
33
|
-
|
|
34
|
-
<div class="control-group">
|
|
35
|
-
<div style="padding: 1rem; background: #ebf8ff; border: 1px solid #90cdf4; border-radius: 0.5rem;">
|
|
36
|
-
<p style="color: #2b6cb0; font-size: 0.875rem;">
|
|
37
|
-
<strong>Tip:</strong> You can also trigger context menus from your app by right-clicking in your UI and preventing the default action.
|
|
38
|
-
</p>
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
|
|
43
|
-
<div class="demo-results">
|
|
44
|
-
<div class="results-header">Menu Events:</div>
|
|
45
|
-
<div id="menu-events" class="menu-events">
|
|
46
|
-
<div class="no-events" style="text-align: center; color: #718096; padding: 2rem;">
|
|
47
|
-
Click menu items to see events here
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
52
|
-
`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
initialize(rpc: any) {
|
|
56
|
-
this.rpc = rpc;
|
|
57
|
-
const showContextBtn = document.getElementById('show-context-menu');
|
|
58
|
-
|
|
59
|
-
showContextBtn?.addEventListener('click', async () => {
|
|
60
|
-
try {
|
|
61
|
-
// Show context menu at a default position
|
|
62
|
-
await rpc.request.showContextMenu({ x: 100, y: 100 });
|
|
63
|
-
this.addMenuEvent('Context menu shown');
|
|
64
|
-
} catch (error) {
|
|
65
|
-
console.error('Error showing context menu:', error);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
private addMenuEvent(event: string) {
|
|
71
|
-
const container = document.getElementById('menu-events');
|
|
72
|
-
if (!container) return;
|
|
73
|
-
|
|
74
|
-
const time = new Date().toLocaleTimeString();
|
|
75
|
-
const eventHtml = `
|
|
76
|
-
<div style="background: #f8fafc; border-left: 3px solid #4299e1; padding: 0.75rem; margin-bottom: 0.5rem; border-radius: 0 0.25rem 0.25rem 0;">
|
|
77
|
-
<div style="color: #718096; font-size: 0.75rem;">${time}</div>
|
|
78
|
-
<div style="color: #2d3748;">${event}</div>
|
|
79
|
-
</div>
|
|
80
|
-
`;
|
|
81
|
-
|
|
82
|
-
// Remove "no events" message if it exists
|
|
83
|
-
const noEvents = container.querySelector('.no-events');
|
|
84
|
-
if (noEvents) {
|
|
85
|
-
container.innerHTML = '';
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Add new event at the top
|
|
89
|
-
container.insertAdjacentHTML('afterbegin', eventHtml);
|
|
90
|
-
|
|
91
|
-
// Keep only last 10 events
|
|
92
|
-
const events = container.children;
|
|
93
|
-
while (events.length > 10) {
|
|
94
|
-
container.removeChild(events[events.length - 1]);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Handle events from the backend
|
|
99
|
-
onMenuClicked(data: { action: string }) {
|
|
100
|
-
this.addMenuEvent(`Menu clicked: ${data.action}`);
|
|
101
|
-
}
|
|
102
|
-
}
|