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.
Files changed (78) hide show
  1. package/{templates/multitab-browser/bun.lock → bun.lock} +20 -13
  2. package/dist/api/bun/proc/native.ts +84 -16
  3. package/package.json +14 -16
  4. package/BETA_RELEASE.md +0 -67
  5. package/BUILD.md +0 -90
  6. package/LICENSE +0 -21
  7. package/README.md +0 -102
  8. package/debug.js +0 -5
  9. package/templates/hello-world/README.md +0 -57
  10. package/templates/hello-world/bun.lock +0 -225
  11. package/templates/hello-world/electrobun.config.ts +0 -28
  12. package/templates/hello-world/package.json +0 -16
  13. package/templates/hello-world/src/bun/index.ts +0 -15
  14. package/templates/hello-world/src/mainview/index.css +0 -124
  15. package/templates/hello-world/src/mainview/index.html +0 -46
  16. package/templates/hello-world/src/mainview/index.ts +0 -1
  17. package/templates/interactive-playground/README.md +0 -26
  18. package/templates/interactive-playground/assets/tray-icon.png +0 -0
  19. package/templates/interactive-playground/electrobun.config.ts +0 -36
  20. package/templates/interactive-playground/package-lock.json +0 -1112
  21. package/templates/interactive-playground/package.json +0 -15
  22. package/templates/interactive-playground/src/bun/demos/files.ts +0 -70
  23. package/templates/interactive-playground/src/bun/demos/menus.ts +0 -139
  24. package/templates/interactive-playground/src/bun/demos/rpc.ts +0 -83
  25. package/templates/interactive-playground/src/bun/demos/system.ts +0 -72
  26. package/templates/interactive-playground/src/bun/demos/updates.ts +0 -105
  27. package/templates/interactive-playground/src/bun/demos/windows.ts +0 -90
  28. package/templates/interactive-playground/src/bun/index.ts +0 -124
  29. package/templates/interactive-playground/src/bun/types/rpc.ts +0 -109
  30. package/templates/interactive-playground/src/mainview/components/EventLog.ts +0 -107
  31. package/templates/interactive-playground/src/mainview/components/Sidebar.ts +0 -65
  32. package/templates/interactive-playground/src/mainview/components/Toast.ts +0 -57
  33. package/templates/interactive-playground/src/mainview/demos/FileDemo.ts +0 -211
  34. package/templates/interactive-playground/src/mainview/demos/MenuDemo.ts +0 -102
  35. package/templates/interactive-playground/src/mainview/demos/RPCDemo.ts +0 -229
  36. package/templates/interactive-playground/src/mainview/demos/TrayDemo.ts +0 -132
  37. package/templates/interactive-playground/src/mainview/demos/WebViewDemo.ts +0 -465
  38. package/templates/interactive-playground/src/mainview/demos/WindowDemo.ts +0 -207
  39. package/templates/interactive-playground/src/mainview/index.css +0 -538
  40. package/templates/interactive-playground/src/mainview/index.html +0 -103
  41. package/templates/interactive-playground/src/mainview/index.ts +0 -238
  42. package/templates/multitab-browser/README.md +0 -34
  43. package/templates/multitab-browser/electrobun.config.ts +0 -32
  44. package/templates/multitab-browser/package-lock.json +0 -20
  45. package/templates/multitab-browser/package.json +0 -12
  46. package/templates/multitab-browser/src/bun/index.ts +0 -144
  47. package/templates/multitab-browser/src/bun/tabManager.ts +0 -200
  48. package/templates/multitab-browser/src/bun/types/rpc.ts +0 -78
  49. package/templates/multitab-browser/src/mainview/index.css +0 -487
  50. package/templates/multitab-browser/src/mainview/index.html +0 -94
  51. package/templates/multitab-browser/src/mainview/index.ts +0 -634
  52. package/templates/photo-booth/README.md +0 -108
  53. package/templates/photo-booth/bun.lock +0 -239
  54. package/templates/photo-booth/electrobun.config.ts +0 -32
  55. package/templates/photo-booth/package.json +0 -17
  56. package/templates/photo-booth/src/bun/index.ts +0 -92
  57. package/templates/photo-booth/src/mainview/index.css +0 -465
  58. package/templates/photo-booth/src/mainview/index.html +0 -124
  59. package/templates/photo-booth/src/mainview/index.ts +0 -499
  60. package/test-new-window-events.ts +0 -26
  61. package/test-new-window.html +0 -75
  62. package/test-npm-install.sh +0 -34
  63. package/tests/bun.lock +0 -14
  64. package/tests/electrobun.config.ts +0 -45
  65. package/tests/package-lock.json +0 -36
  66. package/tests/package.json +0 -13
  67. package/tests/src/bun/index.ts +0 -100
  68. package/tests/src/bun/test-runner.ts +0 -508
  69. package/tests/src/mainview/index.html +0 -110
  70. package/tests/src/mainview/index.ts +0 -458
  71. package/tests/src/mainview/styles/main.css +0 -451
  72. package/tests/src/testviews/tray-test.html +0 -57
  73. package/tests/src/testviews/webview-mask.html +0 -114
  74. package/tests/src/testviews/webview-navigation.html +0 -36
  75. package/tests/src/testviews/window-create.html +0 -17
  76. package/tests/src/testviews/window-events.html +0 -29
  77. package/tests/src/testviews/window-focus.html +0 -37
  78. 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
- }