1/**
2 * Unified GUI Bundle Entry Point
3 *
4 * Single entry point for all WebKit GUI functionality.
5 * Loads immediately and provides modular initialization system.
6 */
7
8// Import all GUI modules
9import { initializeToast, type ToastConfig } from './modules/toast';
10import { initializeOmnibox, type OmniboxInitConfig } from './modules/omnibox';
11import { keyboardService, type KeyboardService } from '../lib/keyboard';
12// Note: color-scheme module is loaded separately at document-start by WebKit
13
14// Global interface for the unified GUI system
15interface DumberGUI {
16 initializeToast: (config?: ToastConfig) => Promise<void>;
17 initializeOmnibox: (config?: OmniboxInitConfig) => Promise<void>;
18 keyboard: KeyboardService;
19 isReady: boolean;
20}
21
22// (legacy suggestions normalization removed; omnibox now fetches suggestions directly via API)
23
24// DOM readiness utility - handles all document ready states
25function whenDOMReady(callback: () => void) {
26 if (document.readyState === 'loading') {
27 document.addEventListener('DOMContentLoaded', callback, { once: true });
28 } else if (document.readyState === 'interactive' && !document.body) {
29 // Interactive but no body yet, wait a bit more
30 setTimeout(() => whenDOMReady(callback), 10);
31 } else {
32 // DOM ready and body exists
33 callback();
34 }
35}
36
37// Install a page-world bridge so Go can call window.__dumber_omnibox_suggestions()
38// Page-world bridge is injected by WebKit at document-start; no-op here to avoid duplicates.
39
40// Create global namespace
41declare global {
42 interface Window {
43 __dumber_gui?: DumberGUI;
44 __dumber_gui_ready?: boolean;
45 __dumber_showToast?: (message: string, duration?: number, type?: 'info' | 'success' | 'error') => number | void;
46 __dumber_showZoomToast?: (level: number) => void;
47 __dumber_keyboard?: KeyboardService;
48 // Legacy compatibility functions for Go bridge
49 __dumber_toggle?: () => void;
50 __dumber_find_open?: (query?: string) => void;
51 __dumber_find_close?: () => void;
52 __dumber_find_query?: (query: string) => void;
53 }
54}
55
56// Prevent multiple initialization with enhanced logging
57if (!window.__dumber_gui_ready) {
58 // Skip initialization in iframes - GUI only needed in main frame
59 if (window.self !== window.top) {
60 console.log('🚫 Dumber GUI skipped in iframe');
61 } else {
62 window.__dumber_gui_ready = true;
63
64 // Initialize Svelte GUI systems immediately
65 whenDOMReady(async () => {
66 try {
67 // Page-world bridge is injected by WebKit; avoid duplicate injection here
68
69 // Initialize toast system first
70 await initializeToast();
71
72 // Verify toast functions are available
73 if (typeof window.__dumber_showToast !== 'function') {
74 throw new Error('Toast functions not exposed after initialization');
75 }
76
77 // Initialize omnibox system
78 await initializeOmnibox();
79
80 console.log('✅ All GUI systems initialized successfully');
81 } catch (e) {
82 console.error('❌ Failed to initialize Svelte toast system:', e);
83
84 // Enhanced fallback with better error recovery
85 window.__dumber_showToast = (message: string, duration: number = 2500) => {
86 // Remove existing toasts to prevent overlap
87 document.querySelectorAll('.dumber-fallback-toast').forEach(el => el.remove());
88
89 const toast = document.createElement('div');
90 toast.className = 'dumber-fallback-toast';
91 toast.textContent = message;
92 toast.style.cssText = `
93 position: fixed;
94 bottom: 20px;
95 right: 20px;
96 background: rgba(0,0,0,0.9);
97 color: white;
98 padding: 12px 16px;
99 border-radius: 6px;
100 z-index: 2147483647;
101 font-family: system-ui, -apple-system, sans-serif;
102 font-size: 14px;
103 opacity: 0;
104 transform: translateX(100%);
105 transition: all 0.3s ease;
106 max-width: 300px;
107 word-wrap: break-word;
108 `;
109
110 if (document.body) {
111 document.body.appendChild(toast);
112
113 // Animate in
114 requestAnimationFrame(() => {
115 toast.style.opacity = '1';
116 toast.style.transform = 'translateX(0)';
117 });
118
119 // Animate out and remove
120 setTimeout(() => {
121 toast.style.opacity = '0';
122 toast.style.transform = 'translateX(100%)';
123 setTimeout(() => toast.remove(), 300);
124 }, duration);
125
126 }
127 };
128
129 window.__dumber_showZoomToast = (level: number) => {
130 const percentage = Math.round(level * 100);
131 window.__dumber_showToast!(`Zoom: ${percentage}%`, 1500);
132 };
133
134 // Expose other required functions
135 window.__dumber_dismissToast = () => {
136 document.querySelectorAll('.dumber-fallback-toast').forEach(el => el.remove());
137 };
138
139 window.__dumber_clearToasts = () => {
140 document.querySelectorAll('.dumber-fallback-toast').forEach(el => el.remove());
141 };
142 }
143 });
144
145 // Expose keyboard service globally for Go bridge
146 window.__dumber_keyboard = keyboardService;
147
148 // Initialize global keyboard and mouse event listeners for the keyboard service
149 whenDOMReady(() => {
150 // Global keyboard event listener (capture phase for high priority)
151 document.addEventListener('keydown', (event) => {
152 keyboardService.handleKeyboardEvent(event);
153 }, true);
154
155 // Global mouse event listener for navigation buttons
156 document.addEventListener('mousedown', (event) => {
157 keyboardService.handleMouseEvent(event);
158 }, true);
159
160 console.log('✅ KeyboardService initialized with global listeners');
161
162 // Listen to bridge keyboard events from page-world and forward to KeyboardService
163 document.addEventListener('dumber:key', (e: Event) => {
164 const detail = (e as CustomEvent).detail || {};
165 if (detail && typeof detail.shortcut === 'string') {
166 keyboardService.handleNativeShortcut(detail.shortcut);
167 }
168 });
169
170 // Suggestions are now fetched via API directly by the omnibox component; no page-bridge handlers needed
171 });
172
173 // Legacy compatibility functions for existing Go code
174 window.__dumber_toggle = () => {
175 try {
176 console.log('🎯 __dumber_toggle called');
177 console.log('🔧 Omnibox API available:', !!window.__dumber_omnibox);
178 if (window.__dumber_omnibox?.toggle) {
179 console.log('✅ Using Svelte omnibox toggle');
180 window.__dumber_omnibox.toggle();
181 return;
182 }
183
184 throw new Error('Omnibox toggle requested but API is unavailable');
185 } catch (error) {
186 console.error('❌ Error in __dumber_toggle:', error);
187 }
188 };
189
190 window.__dumber_find_open = (query?: string) => {
191 try {
192 if (window.__dumber_omnibox?.open) {
193 window.__dumber_omnibox.open('find', query);
194 return;
195 }
196
197 throw new Error('Omnibox find requested but API is unavailable');
198 } catch (error) {
199 console.error('❌ Error in __dumber_find_open:', error);
200 }
201 };
202
203 window.__dumber_find_close = () => {
204 if (window.__dumber_omnibox?.close) {
205 window.__dumber_omnibox.close();
206 return;
207 }
208
209 const error = new Error('Omnibox close requested but API is unavailable');
210 console.error('❌ Error in __dumber_find_close:', error);
211 };
212
213 // Find query function that Go uses to set find query
214 window.__dumber_find_query = (query: string) => {
215 if (window.__dumber_omnibox?.findQuery) {
216 window.__dumber_omnibox.findQuery(query);
217 }
218 };
219
220 // Create the global GUI object
221 window.__dumber_gui = {
222 initializeToast: async (config?: ToastConfig) => {
223 return await initializeToast(config);
224 },
225
226 initializeOmnibox: async (config?: OmniboxInitConfig) => {
227 return await initializeOmnibox(config);
228 },
229
230 keyboard: keyboardService,
231
232 isReady: true
233 };
234
235 }
236}