electrobun 0.5.0-beta.0 → 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,499 +0,0 @@
|
|
|
1
|
-
import Electrobun, { Electroview } from "electrobun/view";
|
|
2
|
-
import type { PhotoBoothRPC } from "../bun/index";
|
|
3
|
-
|
|
4
|
-
// Create RPC client
|
|
5
|
-
const rpc = Electroview.defineRPC<PhotoBoothRPC>({
|
|
6
|
-
maxRequestTime: 5000,
|
|
7
|
-
handlers: {
|
|
8
|
-
requests: {},
|
|
9
|
-
messages: {}
|
|
10
|
-
}
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
// Initialize Electrobun with RPC
|
|
14
|
-
const electrobun = new Electrobun.Electroview({ rpc });
|
|
15
|
-
|
|
16
|
-
interface Photo {
|
|
17
|
-
id: string;
|
|
18
|
-
dataUrl: string;
|
|
19
|
-
timestamp: Date;
|
|
20
|
-
type: 'camera' | 'screen';
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
type CaptureMode = 'camera' | 'screen';
|
|
24
|
-
|
|
25
|
-
class PhotoBooth {
|
|
26
|
-
private video: HTMLVideoElement;
|
|
27
|
-
private canvas: HTMLCanvasElement;
|
|
28
|
-
private captureBtn: HTMLButtonElement;
|
|
29
|
-
private gallery: HTMLElement;
|
|
30
|
-
private cameraSelect: HTMLSelectElement;
|
|
31
|
-
private timerToggle: HTMLInputElement;
|
|
32
|
-
private cameraModeBtn: HTMLButtonElement;
|
|
33
|
-
private screenModeBtn: HTMLButtonElement;
|
|
34
|
-
private startCameraBtn: HTMLButtonElement;
|
|
35
|
-
private selectScreenBtn: HTMLButtonElement;
|
|
36
|
-
private status: HTMLElement;
|
|
37
|
-
private statusText: HTMLElement;
|
|
38
|
-
private countdown: HTMLElement;
|
|
39
|
-
private modal: HTMLElement;
|
|
40
|
-
private modalImage: HTMLImageElement;
|
|
41
|
-
private captureBtnText: HTMLElement;
|
|
42
|
-
private cameraIcon: HTMLElement;
|
|
43
|
-
private screenIcon: HTMLElement;
|
|
44
|
-
|
|
45
|
-
private stream: MediaStream | null = null;
|
|
46
|
-
private photos: Photo[] = [];
|
|
47
|
-
private currentPhotoId: string | null = null;
|
|
48
|
-
private currentMode: CaptureMode = 'camera';
|
|
49
|
-
|
|
50
|
-
constructor() {
|
|
51
|
-
// Get DOM elements
|
|
52
|
-
this.video = document.getElementById('video') as HTMLVideoElement;
|
|
53
|
-
this.canvas = document.getElementById('canvas') as HTMLCanvasElement;
|
|
54
|
-
this.captureBtn = document.getElementById('captureBtn') as HTMLButtonElement;
|
|
55
|
-
this.gallery = document.getElementById('gallery') as HTMLElement;
|
|
56
|
-
this.cameraSelect = document.getElementById('cameraSelect') as HTMLSelectElement;
|
|
57
|
-
this.timerToggle = document.getElementById('timerToggle') as HTMLInputElement;
|
|
58
|
-
this.cameraModeBtn = document.getElementById('cameraModeBtn') as HTMLButtonElement;
|
|
59
|
-
this.screenModeBtn = document.getElementById('screenModeBtn') as HTMLButtonElement;
|
|
60
|
-
this.startCameraBtn = document.getElementById('startCameraBtn') as HTMLButtonElement;
|
|
61
|
-
this.selectScreenBtn = document.getElementById('selectScreenBtn') as HTMLButtonElement;
|
|
62
|
-
this.status = document.getElementById('status') as HTMLElement;
|
|
63
|
-
this.statusText = this.status.querySelector('.status-text') as HTMLElement;
|
|
64
|
-
this.countdown = document.getElementById('countdown') as HTMLElement;
|
|
65
|
-
this.modal = document.getElementById('photoModal') as HTMLElement;
|
|
66
|
-
this.modalImage = document.getElementById('modalImage') as HTMLImageElement;
|
|
67
|
-
this.captureBtnText = this.captureBtn.querySelector('.capture-btn-text') as HTMLElement;
|
|
68
|
-
this.cameraIcon = this.captureBtn.querySelector('.capture-icon-camera') as HTMLElement;
|
|
69
|
-
this.screenIcon = this.captureBtn.querySelector('.capture-icon-screen') as HTMLElement;
|
|
70
|
-
|
|
71
|
-
this.initializeEventListeners();
|
|
72
|
-
this.initializeApp();
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
private initializeEventListeners() {
|
|
76
|
-
// Mode toggle buttons
|
|
77
|
-
this.cameraModeBtn.addEventListener('click', () => this.setMode('camera'));
|
|
78
|
-
this.screenModeBtn.addEventListener('click', () => this.setMode('screen'));
|
|
79
|
-
|
|
80
|
-
// Capture button
|
|
81
|
-
this.captureBtn.addEventListener('click', () => this.capturePhoto());
|
|
82
|
-
|
|
83
|
-
// Camera controls
|
|
84
|
-
this.startCameraBtn.addEventListener('click', () => this.startCamera());
|
|
85
|
-
this.cameraSelect.addEventListener('change', (e) => {
|
|
86
|
-
const deviceId = (e.target as HTMLSelectElement).value;
|
|
87
|
-
if (deviceId) {
|
|
88
|
-
this.switchCamera(deviceId);
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// Screen controls
|
|
93
|
-
this.selectScreenBtn.addEventListener('click', () => this.selectScreen());
|
|
94
|
-
|
|
95
|
-
// Modal controls
|
|
96
|
-
document.getElementById('modalClose')?.addEventListener('click', () => this.closeModal());
|
|
97
|
-
document.getElementById('downloadBtn')?.addEventListener('click', () => this.saveCurrentPhoto());
|
|
98
|
-
document.getElementById('deleteBtn')?.addEventListener('click', () => this.deleteCurrentPhoto());
|
|
99
|
-
|
|
100
|
-
// Close modal on background click
|
|
101
|
-
this.modal.addEventListener('click', (e) => {
|
|
102
|
-
if (e.target === this.modal) {
|
|
103
|
-
this.closeModal();
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private async initializeApp() {
|
|
109
|
-
// Set initial mode
|
|
110
|
-
this.setMode('camera');
|
|
111
|
-
|
|
112
|
-
// Check available cameras
|
|
113
|
-
await this.populateCameraList();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private setMode(mode: CaptureMode) {
|
|
117
|
-
this.currentMode = mode;
|
|
118
|
-
|
|
119
|
-
// Update UI classes
|
|
120
|
-
document.body.classList.toggle('mode-screen', mode === 'screen');
|
|
121
|
-
|
|
122
|
-
// Update mode buttons
|
|
123
|
-
this.cameraModeBtn.classList.toggle('active', mode === 'camera');
|
|
124
|
-
this.screenModeBtn.classList.toggle('active', mode === 'screen');
|
|
125
|
-
|
|
126
|
-
// Update capture button
|
|
127
|
-
this.cameraIcon.style.display = mode === 'camera' ? 'block' : 'none';
|
|
128
|
-
this.screenIcon.style.display = mode === 'screen' ? 'block' : 'none';
|
|
129
|
-
this.captureBtnText.textContent = mode === 'camera' ? 'Take Photo' : 'Take Screenshot';
|
|
130
|
-
|
|
131
|
-
// Reset state when switching modes
|
|
132
|
-
this.stopStream();
|
|
133
|
-
this.captureBtn.disabled = true;
|
|
134
|
-
|
|
135
|
-
// Reset video display and hide any placeholders
|
|
136
|
-
this.video.style.display = 'block';
|
|
137
|
-
const placeholder = this.video.parentElement?.querySelector('.native-capture-placeholder') as HTMLElement;
|
|
138
|
-
if (placeholder) {
|
|
139
|
-
placeholder.style.display = 'none';
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Update status based on mode
|
|
143
|
-
if (mode === 'camera') {
|
|
144
|
-
this.setStatus('Click "Start Camera" to begin', false);
|
|
145
|
-
this.startCameraBtn.style.display = 'flex';
|
|
146
|
-
this.selectScreenBtn.style.display = 'none';
|
|
147
|
-
} else {
|
|
148
|
-
this.setStatus('Screen capture mode - tests getDisplayMedia browser API', false);
|
|
149
|
-
this.selectScreenBtn.style.display = 'flex';
|
|
150
|
-
this.startCameraBtn.style.display = 'none';
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
private stopStream() {
|
|
155
|
-
if (this.stream) {
|
|
156
|
-
this.stream.getTracks().forEach(track => track.stop());
|
|
157
|
-
this.stream = null;
|
|
158
|
-
this.video.srcObject = null;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
private async populateCameraList() {
|
|
163
|
-
try {
|
|
164
|
-
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
165
|
-
const videoDevices = devices.filter(device => device.kind === 'videoinput');
|
|
166
|
-
|
|
167
|
-
// Clear existing options
|
|
168
|
-
this.cameraSelect.innerHTML = '<option value="">Select Camera</option>';
|
|
169
|
-
|
|
170
|
-
// Add camera options
|
|
171
|
-
videoDevices.forEach((device, index) => {
|
|
172
|
-
const option = document.createElement('option');
|
|
173
|
-
option.value = device.deviceId;
|
|
174
|
-
option.textContent = device.label || `Camera ${index + 1}`;
|
|
175
|
-
this.cameraSelect.appendChild(option);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
if (videoDevices.length > 0) {
|
|
179
|
-
this.startCameraBtn.style.display = 'flex';
|
|
180
|
-
} else {
|
|
181
|
-
this.setStatus('No cameras found on this device', false);
|
|
182
|
-
}
|
|
183
|
-
} catch (error) {
|
|
184
|
-
console.error('Error enumerating cameras:', error);
|
|
185
|
-
this.setStatus('Unable to access camera list', false);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
private async startCamera() {
|
|
190
|
-
try {
|
|
191
|
-
const constraints: MediaStreamConstraints = {
|
|
192
|
-
video: {
|
|
193
|
-
width: { ideal: 1280 },
|
|
194
|
-
height: { ideal: 720 }
|
|
195
|
-
},
|
|
196
|
-
audio: false
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
// If a specific camera is selected, use it
|
|
200
|
-
const selectedCamera = this.cameraSelect.value;
|
|
201
|
-
if (selectedCamera) {
|
|
202
|
-
(constraints.video as MediaTrackConstraints).deviceId = selectedCamera;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
this.stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
206
|
-
this.video.srcObject = this.stream;
|
|
207
|
-
|
|
208
|
-
// Update status and enable capture
|
|
209
|
-
this.setStatus('Camera active - ready to take photos', true);
|
|
210
|
-
this.captureBtn.disabled = false;
|
|
211
|
-
this.startCameraBtn.style.display = 'none';
|
|
212
|
-
|
|
213
|
-
} catch (error) {
|
|
214
|
-
console.error('Error starting camera:', error);
|
|
215
|
-
this.setStatus(`Camera error: ${(error as Error).message}`, false);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
private async switchCamera(deviceId: string) {
|
|
220
|
-
if (this.stream) {
|
|
221
|
-
this.stopStream();
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
try {
|
|
225
|
-
const constraints: MediaStreamConstraints = {
|
|
226
|
-
video: {
|
|
227
|
-
deviceId: deviceId,
|
|
228
|
-
width: { ideal: 1280 },
|
|
229
|
-
height: { ideal: 720 }
|
|
230
|
-
},
|
|
231
|
-
audio: false
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
this.stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
235
|
-
this.video.srcObject = this.stream;
|
|
236
|
-
this.setStatus('Camera switched successfully', true);
|
|
237
|
-
this.captureBtn.disabled = false;
|
|
238
|
-
} catch (error) {
|
|
239
|
-
console.error('Error switching camera:', error);
|
|
240
|
-
this.setStatus('Failed to switch camera', false);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
private async selectScreen() {
|
|
245
|
-
try {
|
|
246
|
-
// Log what's available for debugging
|
|
247
|
-
console.log('Browser capabilities:');
|
|
248
|
-
console.log(' navigator.mediaDevices:', !!navigator.mediaDevices);
|
|
249
|
-
console.log(' getDisplayMedia:', !!(navigator.mediaDevices && (navigator.mediaDevices as any).getDisplayMedia));
|
|
250
|
-
console.log(' getUserMedia:', !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia));
|
|
251
|
-
console.log(' User agent:', navigator.userAgent);
|
|
252
|
-
|
|
253
|
-
// Check if getDisplayMedia is available
|
|
254
|
-
if (navigator.mediaDevices && (navigator.mediaDevices as any).getDisplayMedia) {
|
|
255
|
-
console.log('getDisplayMedia is available, attempting screen capture');
|
|
256
|
-
|
|
257
|
-
try {
|
|
258
|
-
this.stream = await (navigator.mediaDevices as any).getDisplayMedia({
|
|
259
|
-
video: true,
|
|
260
|
-
audio: false
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
this.video.srcObject = this.stream;
|
|
264
|
-
this.setStatus('Screen capture active - ready to take screenshots', true);
|
|
265
|
-
this.captureBtn.disabled = false;
|
|
266
|
-
this.selectScreenBtn.style.display = 'none';
|
|
267
|
-
|
|
268
|
-
// Listen for when the user stops sharing
|
|
269
|
-
if (this.stream) {
|
|
270
|
-
const videoTracks = this.stream.getVideoTracks();
|
|
271
|
-
if (videoTracks.length > 0) {
|
|
272
|
-
videoTracks[0].addEventListener('ended', () => {
|
|
273
|
-
this.setStatus('Screen sharing stopped', false);
|
|
274
|
-
this.captureBtn.disabled = true;
|
|
275
|
-
this.selectScreenBtn.style.display = 'flex';
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
} catch (permissionError) {
|
|
280
|
-
// Handle permission denial or other getDisplayMedia errors
|
|
281
|
-
console.log('getDisplayMedia failed:', permissionError);
|
|
282
|
-
throw new Error(`Screen capture failed: ${(permissionError as Error).message}`);
|
|
283
|
-
}
|
|
284
|
-
} else {
|
|
285
|
-
// getDisplayMedia not available
|
|
286
|
-
console.log('getDisplayMedia not available in this browser');
|
|
287
|
-
throw new Error('getDisplayMedia API is not available in this browser. This may be due to:\n• WKWebView limitations\n• Browser version\n• Security restrictions\n• Platform limitations');
|
|
288
|
-
}
|
|
289
|
-
} catch (error) {
|
|
290
|
-
console.error('Error selecting screen:', error);
|
|
291
|
-
this.setStatus(`Screen capture error: ${(error as Error).message}`, false);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
private async capturePhoto() {
|
|
296
|
-
if (this.currentMode === 'camera') {
|
|
297
|
-
await this.captureCameraPhoto();
|
|
298
|
-
} else {
|
|
299
|
-
await this.captureScreenshot();
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
private async captureCameraPhoto() {
|
|
304
|
-
if (!this.stream) {
|
|
305
|
-
this.setStatus('No camera stream available', false);
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
try {
|
|
310
|
-
// Optional timer countdown
|
|
311
|
-
if (this.timerToggle.checked) {
|
|
312
|
-
await this.showCountdown();
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Capture from video stream
|
|
316
|
-
const context = this.canvas.getContext('2d');
|
|
317
|
-
if (!context) return;
|
|
318
|
-
|
|
319
|
-
this.canvas.width = this.video.videoWidth;
|
|
320
|
-
this.canvas.height = this.video.videoHeight;
|
|
321
|
-
context.drawImage(this.video, 0, 0);
|
|
322
|
-
|
|
323
|
-
// Convert to data URL
|
|
324
|
-
const dataUrl = this.canvas.toDataURL('image/png');
|
|
325
|
-
|
|
326
|
-
// Add to gallery
|
|
327
|
-
const photo: Photo = {
|
|
328
|
-
id: Date.now().toString(),
|
|
329
|
-
dataUrl: dataUrl,
|
|
330
|
-
timestamp: new Date(),
|
|
331
|
-
type: 'camera'
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
this.photos.push(photo);
|
|
335
|
-
this.addPhotoToGallery(photo);
|
|
336
|
-
this.setStatus('Photo captured!', true);
|
|
337
|
-
this.playCaptureFeedback();
|
|
338
|
-
|
|
339
|
-
} catch (error) {
|
|
340
|
-
console.error('Error capturing photo:', error);
|
|
341
|
-
this.setStatus(`Capture failed: ${(error as Error).message}`, false);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
private async captureScreenshot() {
|
|
346
|
-
try {
|
|
347
|
-
if (this.stream) {
|
|
348
|
-
// We have a screen share stream from getDisplayMedia - capture it
|
|
349
|
-
await this.captureCameraPhoto(); // Same capture logic, but from screen stream
|
|
350
|
-
} else {
|
|
351
|
-
// No stream available - this shouldn't happen if selectScreen worked
|
|
352
|
-
throw new Error('No screen capture stream available. Make sure to select a screen first.');
|
|
353
|
-
}
|
|
354
|
-
} catch (error) {
|
|
355
|
-
console.error('Error capturing screenshot:', error);
|
|
356
|
-
this.setStatus(`Screenshot failed: ${(error as Error).message}`, false);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
private async showCountdown() {
|
|
361
|
-
for (let i = 3; i > 0; i--) {
|
|
362
|
-
this.countdown.textContent = i.toString();
|
|
363
|
-
this.countdown.style.display = 'flex';
|
|
364
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
365
|
-
}
|
|
366
|
-
this.countdown.style.display = 'none';
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
private playCaptureFeedback() {
|
|
370
|
-
// Flash effect
|
|
371
|
-
document.body.style.backgroundColor = 'white';
|
|
372
|
-
setTimeout(() => {
|
|
373
|
-
document.body.style.backgroundColor = '';
|
|
374
|
-
}, 100);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
private addPhotoToGallery(photo: Photo) {
|
|
378
|
-
// Remove empty state if it exists
|
|
379
|
-
const emptyState = this.gallery.querySelector('.empty-state');
|
|
380
|
-
if (emptyState) {
|
|
381
|
-
emptyState.remove();
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
// Create photo element
|
|
385
|
-
const photoElement = document.createElement('div');
|
|
386
|
-
photoElement.className = 'photo-item';
|
|
387
|
-
photoElement.dataset['photoId'] = photo.id;
|
|
388
|
-
|
|
389
|
-
const typeIcon = photo.type === 'camera' ? '📷' : '🖥️';
|
|
390
|
-
photoElement.innerHTML = `
|
|
391
|
-
<img src="${photo.dataUrl}" alt="Captured ${photo.type}">
|
|
392
|
-
<div class="photo-info">
|
|
393
|
-
<span class="photo-type">${typeIcon}</span>
|
|
394
|
-
<span class="photo-time">${photo.timestamp.toLocaleTimeString()}</span>
|
|
395
|
-
</div>
|
|
396
|
-
`;
|
|
397
|
-
|
|
398
|
-
photoElement.addEventListener('click', () => this.openModal(photo.id));
|
|
399
|
-
this.gallery.insertBefore(photoElement, this.gallery.firstChild);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
private openModal(photoId: string) {
|
|
403
|
-
const photo = this.photos.find(p => p.id === photoId);
|
|
404
|
-
if (!photo) return;
|
|
405
|
-
|
|
406
|
-
this.currentPhotoId = photoId;
|
|
407
|
-
this.modalImage.src = photo.dataUrl;
|
|
408
|
-
this.modal.style.display = 'flex';
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
private closeModal() {
|
|
412
|
-
this.modal.style.display = 'none';
|
|
413
|
-
this.currentPhotoId = null;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
private async saveCurrentPhoto() {
|
|
417
|
-
if (!this.currentPhotoId) return;
|
|
418
|
-
|
|
419
|
-
const photo = this.photos.find(p => p.id === this.currentPhotoId);
|
|
420
|
-
if (!photo) return;
|
|
421
|
-
|
|
422
|
-
try {
|
|
423
|
-
const filename = `${photo.type}-${new Date().toISOString().slice(0, 19).replace(/[:.]/g, '-')}.png`;
|
|
424
|
-
const result = await electrobun.rpc!.request.savePhoto({
|
|
425
|
-
dataUrl: photo.dataUrl,
|
|
426
|
-
filename: filename
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
if (result.success) {
|
|
430
|
-
this.showStatus('Photo saved successfully!', 'success');
|
|
431
|
-
if (result.path) {
|
|
432
|
-
console.log('Photo saved to:', result.path);
|
|
433
|
-
}
|
|
434
|
-
} else if (result.reason === 'canceled') {
|
|
435
|
-
this.showStatus('Save canceled', 'info');
|
|
436
|
-
} else {
|
|
437
|
-
this.showStatus('Failed to save photo', 'error');
|
|
438
|
-
}
|
|
439
|
-
} catch (error) {
|
|
440
|
-
console.error('Error saving photo:', error);
|
|
441
|
-
this.showStatus('Error saving photo', 'error');
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
private deleteCurrentPhoto() {
|
|
446
|
-
if (!this.currentPhotoId) return;
|
|
447
|
-
|
|
448
|
-
const photoIndex = this.photos.findIndex(p => p.id === this.currentPhotoId);
|
|
449
|
-
if (photoIndex === -1) return;
|
|
450
|
-
|
|
451
|
-
// Remove from array
|
|
452
|
-
this.photos.splice(photoIndex, 1);
|
|
453
|
-
|
|
454
|
-
// Remove from DOM
|
|
455
|
-
const photoElement = this.gallery.querySelector(`[data-photo-id="${this.currentPhotoId}"]`);
|
|
456
|
-
if (photoElement) {
|
|
457
|
-
photoElement.remove();
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// Show empty state if no photos left
|
|
461
|
-
if (this.photos.length === 0) {
|
|
462
|
-
this.gallery.innerHTML = `
|
|
463
|
-
<div class="empty-state">
|
|
464
|
-
No photos/screenshots yet. Take some photos or screenshots to get started!
|
|
465
|
-
</div>
|
|
466
|
-
`;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
this.closeModal();
|
|
470
|
-
this.showStatus('Photo deleted', 'info');
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
private setStatus(message: string, active: boolean, error: boolean = false) {
|
|
474
|
-
this.statusText.textContent = message;
|
|
475
|
-
this.status.classList.toggle('active', active && !error);
|
|
476
|
-
this.status.classList.toggle('error', error);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
private showStatus(message: string, type: 'success' | 'error' | 'info') {
|
|
480
|
-
console.log(`[${type}] ${message}`);
|
|
481
|
-
|
|
482
|
-
// Update status bar temporarily
|
|
483
|
-
const originalText = this.statusText.textContent;
|
|
484
|
-
const originalClasses = this.status.className;
|
|
485
|
-
|
|
486
|
-
this.setStatus(message, type === 'success', type === 'error');
|
|
487
|
-
|
|
488
|
-
// Restore original status after 3 seconds
|
|
489
|
-
setTimeout(() => {
|
|
490
|
-
this.statusText.textContent = originalText;
|
|
491
|
-
this.status.className = originalClasses;
|
|
492
|
-
}, 3000);
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// Initialize the app when DOM is loaded
|
|
497
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
498
|
-
new PhotoBooth();
|
|
499
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { BrowserWindow } from "./src/bun/core/BrowserWindow";
|
|
2
|
-
|
|
3
|
-
const win = new BrowserWindow({
|
|
4
|
-
title: "New Window Event Test",
|
|
5
|
-
frame: {
|
|
6
|
-
width: 800,
|
|
7
|
-
height: 600,
|
|
8
|
-
x: 100,
|
|
9
|
-
y: 100
|
|
10
|
-
},
|
|
11
|
-
url: null,
|
|
12
|
-
html: null,
|
|
13
|
-
renderer: 'native', // Test with native WebKit first
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
// Listen for new-window-open events
|
|
17
|
-
win.webview.on("new-window-open", (event) => {
|
|
18
|
-
console.log("🚀 NEW-WINDOW-OPEN EVENT:", event.detail);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
// Load our test HTML
|
|
22
|
-
const testHTML = await Bun.file("./test-new-window.html").text();
|
|
23
|
-
win.webview.loadHTML(testHTML);
|
|
24
|
-
|
|
25
|
-
console.log("Test window created. Try clicking links with and without CMD key.");
|
|
26
|
-
console.log("Watch for 'NEW-WINDOW-OPEN EVENT' in the console.");
|
package/test-new-window.html
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>New Window Event Test</title>
|
|
5
|
-
<style>
|
|
6
|
-
body {
|
|
7
|
-
font-family: system-ui;
|
|
8
|
-
padding: 20px;
|
|
9
|
-
max-width: 600px;
|
|
10
|
-
margin: 0 auto;
|
|
11
|
-
}
|
|
12
|
-
a, button {
|
|
13
|
-
display: block;
|
|
14
|
-
margin: 15px 0;
|
|
15
|
-
padding: 10px;
|
|
16
|
-
background: #f0f0f0;
|
|
17
|
-
text-decoration: none;
|
|
18
|
-
color: #333;
|
|
19
|
-
border: 1px solid #ddd;
|
|
20
|
-
cursor: pointer;
|
|
21
|
-
}
|
|
22
|
-
a:hover, button:hover {
|
|
23
|
-
background: #e0e0e0;
|
|
24
|
-
}
|
|
25
|
-
h2 {
|
|
26
|
-
color: #444;
|
|
27
|
-
border-bottom: 2px solid #ddd;
|
|
28
|
-
padding-bottom: 10px;
|
|
29
|
-
}
|
|
30
|
-
.note {
|
|
31
|
-
background: #fffbdd;
|
|
32
|
-
padding: 10px;
|
|
33
|
-
border-left: 4px solid #ffc107;
|
|
34
|
-
margin: 20px 0;
|
|
35
|
-
}
|
|
36
|
-
</style>
|
|
37
|
-
</head>
|
|
38
|
-
<body>
|
|
39
|
-
<h1>Test New Window Events</h1>
|
|
40
|
-
|
|
41
|
-
<div class="note">
|
|
42
|
-
<strong>Instructions:</strong> Open the console and watch for "new-window-open" events.
|
|
43
|
-
Try each link/button below with and without holding CMD key.
|
|
44
|
-
</div>
|
|
45
|
-
|
|
46
|
-
<h2>Links that SHOULD fire new-window-open:</h2>
|
|
47
|
-
<a href="https://example.com" target="_blank">1. Link with target="_blank"</a>
|
|
48
|
-
<a href="https://example.com" target="newwindow">2. Link with target="newwindow"</a>
|
|
49
|
-
<button onclick="window.open('https://example.com', '_blank')">3. JavaScript window.open()</button>
|
|
50
|
-
|
|
51
|
-
<h2>Links that should NOT fire (without CMD):</h2>
|
|
52
|
-
<a href="https://example.com">4. Regular link (no target)</a>
|
|
53
|
-
<a href="https://example.com" target="_self">5. Link with target="_self"</a>
|
|
54
|
-
|
|
55
|
-
<h2>CMD+Click Test:</h2>
|
|
56
|
-
<p>Hold CMD and click this to see if it fires the event:</p>
|
|
57
|
-
<a href="https://example.com">6. CMD+Click me (regular link)</a>
|
|
58
|
-
|
|
59
|
-
<script>
|
|
60
|
-
// Log all clicks for debugging
|
|
61
|
-
document.addEventListener('click', (e) => {
|
|
62
|
-
if (e.target.tagName === 'A') {
|
|
63
|
-
console.log('Link clicked:', {
|
|
64
|
-
href: e.target.href,
|
|
65
|
-
target: e.target.target,
|
|
66
|
-
cmdKey: e.metaKey,
|
|
67
|
-
ctrlKey: e.ctrlKey,
|
|
68
|
-
shiftKey: e.shiftKey,
|
|
69
|
-
altKey: e.altKey
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
</script>
|
|
74
|
-
</body>
|
|
75
|
-
</html>
|
package/test-npm-install.sh
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# Test npm installation locally
|
|
4
|
-
echo "Testing Electrobun npm installation..."
|
|
5
|
-
|
|
6
|
-
# Create a temporary test directory
|
|
7
|
-
TEST_DIR=$(mktemp -d)
|
|
8
|
-
cd $TEST_DIR
|
|
9
|
-
|
|
10
|
-
echo "Test directory: $TEST_DIR"
|
|
11
|
-
|
|
12
|
-
# Initialize a test project
|
|
13
|
-
npm init -y
|
|
14
|
-
|
|
15
|
-
# Set environment variable to use specific version
|
|
16
|
-
export ELECTROBUN_VERSION=v0.0.19-beta.1
|
|
17
|
-
|
|
18
|
-
# Install electrobun (will use local package.json)
|
|
19
|
-
npm install file:///home/yoav/code/electrobun
|
|
20
|
-
|
|
21
|
-
# Check if installation worked
|
|
22
|
-
if [ -f "node_modules/electrobun/dist/electrobun" ]; then
|
|
23
|
-
echo "✓ Electrobun CLI installed successfully"
|
|
24
|
-
./node_modules/.bin/electrobun --version
|
|
25
|
-
else
|
|
26
|
-
echo "✗ Electrobun CLI not found"
|
|
27
|
-
fi
|
|
28
|
-
|
|
29
|
-
# List installed files
|
|
30
|
-
echo -e "\nInstalled files:"
|
|
31
|
-
find node_modules/electrobun -type f -name "*.ts" | head -10
|
|
32
|
-
find node_modules/electrobun/dist -type f | head -10
|
|
33
|
-
|
|
34
|
-
echo -e "\nTest complete. Directory: $TEST_DIR"
|
package/tests/bun.lock
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"lockfileVersion": 1,
|
|
3
|
-
"workspaces": {
|
|
4
|
-
"": {
|
|
5
|
-
"name": "electrobun-tests",
|
|
6
|
-
"dependencies": {
|
|
7
|
-
"electrobun": "file:..",
|
|
8
|
-
},
|
|
9
|
-
},
|
|
10
|
-
},
|
|
11
|
-
"packages": {
|
|
12
|
-
"electrobun": ["..@file:..", { "bin": { "electrobun": "bin/electrobun.cjs" } }],
|
|
13
|
-
}
|
|
14
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
app: {
|
|
3
|
-
name: "Electrobun Test Harness",
|
|
4
|
-
identifier: "dev.electrobun.tests",
|
|
5
|
-
version: "1.0.0",
|
|
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
|
-
webviewtag: {
|
|
18
|
-
entrypoint: "src/webviewtag/index.ts",
|
|
19
|
-
external: [],
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
copy: {
|
|
23
|
-
"src/mainview/index.html": "views/mainview/index.html",
|
|
24
|
-
"src/mainview/styles/main.css": "views/mainview/styles/main.css",
|
|
25
|
-
"src/testviews/window-create.html": "views/testviews/window-create.html",
|
|
26
|
-
"src/testviews/window-events.html": "views/testviews/window-events.html",
|
|
27
|
-
"src/testviews/webview-mask.html": "views/testviews/webview-mask.html",
|
|
28
|
-
"src/testviews/webview-navigation.html": "views/testviews/webview-navigation.html",
|
|
29
|
-
"src/testviews/tray-test.html": "views/testviews/tray-test.html",
|
|
30
|
-
"src/testviews/window-focus.html": "views/testviews/window-focus.html",
|
|
31
|
-
},
|
|
32
|
-
mac: {
|
|
33
|
-
codesign: false,
|
|
34
|
-
notarize: false,
|
|
35
|
-
bundleCEF: true,
|
|
36
|
-
entitlements: {},
|
|
37
|
-
},
|
|
38
|
-
linux: {
|
|
39
|
-
bundleCEF: true,
|
|
40
|
-
},
|
|
41
|
-
win: {
|
|
42
|
-
bundleCEF: true,
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
};
|