1package control
2
3import (
4 "context"
5 "fmt"
6 "log"
7
8 "github.com/bnema/dumber/pkg/webkit"
9 "github.com/bnema/dumber/services"
10)
11
12// ZoomController manages zoom functionality for the WebView
13type ZoomController struct {
14 currentURL string
15 lastZoomDomain string
16 programmaticChange bool
17 browserService *services.BrowserService
18 webView *webkit.WebView
19}
20
21// WebViewInterface defines the interface for WebView zoom operations
22type WebViewInterface interface {
23 GetCurrentURL() string
24 SetZoom(level float64) error
25 InjectScript(script string) error
26 RegisterZoomChangedHandler(handler func(float64))
27 RegisterURIChangedHandler(handler func(string))
28}
29
30// NewZoomController creates a new zoom controller
31func NewZoomController(browserService *services.BrowserService, webView *webkit.WebView) *ZoomController {
32 return &ZoomController{
33 currentURL: "dumb://homepage",
34 browserService: browserService,
35 webView: webView,
36 }
37}
38
39// RegisterHandlers sets up the zoom-related event handlers
40func (z *ZoomController) RegisterHandlers() {
41 z.webView.RegisterZoomChangedHandler(z.handleZoomChange)
42 z.webView.RegisterURIChangedHandler(z.handleURIChange)
43}
44
45// handleURIChange responds to URL changes and applies saved zoom levels
46func (z *ZoomController) handleURIChange(url string) {
47 z.currentURL = url
48 if url == "" {
49 return
50 }
51 ctx := context.Background()
52 currentDomain := services.ZoomKeyForLog(url)
53
54 if zoomLevel, err := z.browserService.GetZoomLevel(ctx, url); err == nil {
55 z.programmaticChange = true
56 if err := z.webView.SetZoom(zoomLevel); err == nil {
57 log.Printf("[zoom] loaded %.2f for %s", zoomLevel, currentDomain)
58
59 // Only show toast when entering a new domain (not on first load)
60 if currentDomain != z.lastZoomDomain && z.lastZoomDomain != "" {
61 z.showZoomToast(zoomLevel)
62 }
63 z.lastZoomDomain = currentDomain
64 }
65 z.programmaticChange = false
66 }
67}
68
69// handleZoomChange responds to zoom level changes and persists them
70func (z *ZoomController) handleZoomChange(level float64) {
71 url := z.webView.GetCurrentURL()
72 if url == "" {
73 return
74 }
75 ctx := context.Background()
76 if err := z.browserService.SetZoomLevel(ctx, url, level); err != nil {
77 log.Printf("[zoom] failed to save level %.2f for %s: %v", level, url, err)
78 return
79 }
80 key := services.ZoomKeyForLog(url)
81 log.Printf("[zoom] saved %.2f for %s", level, key)
82
83 // Only show toast for user-initiated zoom changes, not programmatic ones
84 if !z.programmaticChange {
85 z.showZoomToast(level)
86 }
87}
88
89// showZoomToast displays a zoom level notification using TypeScript toast system
90func (z *ZoomController) showZoomToast(level float64) {
91 log.Printf("[zoom] Attempting to show zoom toast for level %.2f", level)
92
93 percentage := int(level * 100)
94
95 // Enhanced approach that ensures toast always appears
96 js := fmt.Sprintf(`(function() {
97 try {
98 console.log('[dumber] Attempting to show zoom toast...');
99 const zoomLevel = %f;
100 const percentage = %d;
101 let toastShown = false;
102
103 // Prefer unified page-world API
104 if (window.__dumber && window.__dumber.toast && typeof window.__dumber.toast.zoom === 'function') {
105 console.log('[dumber] Using unified toast.zoom');
106 window.__dumber.toast.zoom(zoomLevel);
107 toastShown = true;
108 } else if (typeof window.__dumber_showZoomToast === 'function') {
109 console.log('[dumber] Using legacy zoom toast');
110 window.__dumber_showZoomToast(zoomLevel);
111 toastShown = true;
112 } else if (window.__dumber && window.__dumber.toast && typeof window.__dumber.toast.show === 'function') {
113 console.log('[dumber] Using unified toast.show');
114 window.__dumber.toast.show('Zoom: ' + percentage + '%%', 1500, 'info');
115 toastShown = true;
116 } else if (typeof window.__dumber_showToast === 'function') {
117 console.log('[dumber] Using legacy toast.show');
118 window.__dumber_showToast('Zoom: ' + percentage + '%%', 1500, 'info');
119 toastShown = true;
120 }
121
122
123 if (!toastShown) {
124 console.warn('[dumber] Failed to show zoom toast - no DOM body available');
125 }
126
127 } catch(e) {
128 console.error('[dumber] Zoom toast error:', e);
129 }
130 })();`, level, percentage)
131
132 if err := z.webView.InjectScript(js); err != nil {
133 log.Printf("[zoom] failed to show toast: %v", err)
134 } else {
135 log.Printf("[zoom] Toast script injected successfully")
136 }
137}
138
139// ApplyInitialZoom sets the initial zoom level for the current URL
140func (z *ZoomController) ApplyInitialZoom() {
141 ctx := context.Background()
142 url := z.webView.GetCurrentURL()
143 if url == "" {
144 url = "dumb://homepage"
145 }
146 if zoomLevel, err := z.browserService.GetZoomLevel(ctx, url); err == nil {
147 z.programmaticChange = true
148 if err := z.webView.SetZoom(zoomLevel); err != nil {
149 log.Printf("Warning: failed to set initial zoom: %v", err)
150 } else {
151 key := services.ZoomKeyForLog(url)
152 log.Printf("[zoom] loaded %.2f for %s", zoomLevel, key)
153 }
154 z.programmaticChange = false
155 }
156}
157
158// ApplyZoomForURL applies zoom for a specific URL (used for navigation)
159func (z *ZoomController) ApplyZoomForURL(url string) {
160 ctx := context.Background()
161 if zoomLevel, err := z.browserService.GetZoomLevel(ctx, url); err == nil {
162 z.programmaticChange = true
163 if err := z.webView.SetZoom(zoomLevel); err != nil {
164 log.Printf("Warning: failed to set zoom: %v", err)
165 } else {
166 key := services.ZoomKeyForLog(url)
167 log.Printf("[zoom] loaded %.2f for %s", zoomLevel, key)
168 }
169 z.programmaticChange = false
170 }
171}