v0.9.0 636c44c

zoom.go RawHistoryFolder

1package control
2
3import (
4	"context"
5	"log"
6	"time"
7
8	"github.com/bnema/dumber/internal/services"
9	"github.com/bnema/dumber/pkg/webkit"
10)
11
12// ZoomController manages zoom functionality for the WebView
13type ZoomController struct {
14	currentURL         string
15	lastZoomDomain     string
16	programmaticChange bool
17	programmaticTimer  *time.Timer
18	browserService     *services.BrowserService
19	webView            *webkit.WebView
20}
21
22// WebViewInterface defines the interface for WebView zoom operations
23type WebViewInterface interface {
24	GetCurrentURL() string
25	SetZoom(level float64) error
26	InjectScript(script string) error
27	RegisterZoomChangedHandler(handler func(float64))
28	RegisterURIChangedHandler(handler func(string))
29}
30
31// NewZoomController creates a new zoom controller
32func NewZoomController(browserService *services.BrowserService, webView *webkit.WebView) *ZoomController {
33	return &ZoomController{
34		currentURL:     "dumb://homepage",
35		browserService: browserService,
36		webView:        webView,
37	}
38}
39
40// RegisterHandlers sets up the zoom-related event handlers
41func (z *ZoomController) RegisterHandlers() {
42	z.webView.RegisterZoomChangedHandler(z.handleZoomChange)
43	z.webView.RegisterURIChangedHandler(z.handleURIChange)
44}
45
46// handleURIChange responds to URL changes and applies saved zoom levels
47func (z *ZoomController) handleURIChange(url string) {
48	z.currentURL = url
49	if url == "" {
50		return
51	}
52	currentDomain := services.ZoomKeyForLog(url)
53	z.loadZoomLevelAsync(url, currentDomain, false)
54}
55
56// handleZoomChange responds to zoom level changes and persists them
57func (z *ZoomController) handleZoomChange(level float64) {
58	url := z.webView.GetCurrentURL()
59	if url == "" {
60		return
61	}
62	
63	go func(url string, level float64) {
64		ctx := context.Background()
65		if err := z.browserService.SetZoomLevel(ctx, url, level); err != nil {
66			log.Printf("[zoom] failed to save level %.2f for %s: %v", level, url, err)
67			return
68		}
69		key := services.ZoomKeyForLog(url)
70		log.Printf("[zoom] saved %.2f for %s", level, key)
71	}(url, level)
72
73	// Only show toast for user-initiated zoom changes, not programmatic ones
74	if !z.programmaticChange {
75		z.showZoomToast(level)
76	}
77}
78
79// showZoomToast displays a zoom level notification using TypeScript toast system
80func (z *ZoomController) showZoomToast(level float64) {
81	log.Printf("[zoom] Attempting to show zoom toast for level %.2f", level)
82
83	if z.webView == nil {
84		log.Printf("[zoom] webview unavailable for zoom toast")
85		return
86	}
87
88	if err := z.webView.DispatchCustomEvent("dumber:toast:zoom", map[string]any{"level": level}); err != nil {
89		log.Printf("[zoom] failed to dispatch zoom toast: %v", err)
90	}
91}
92
93// ApplyInitialZoom sets the initial zoom level for the current URL
94func (z *ZoomController) ApplyInitialZoom() {
95	ctx := context.Background()
96	url := z.webView.GetCurrentURL()
97	if url == "" {
98		url = "dumb://homepage"
99	}
100	if zoomLevel, err := z.browserService.GetZoomLevel(ctx, url); err == nil {
101		z.applyZoomLevel(url, services.ZoomKeyForLog(url), zoomLevel, false)
102	}
103}
104
105// ApplyZoomForURL applies zoom for a specific URL (used for navigation)
106func (z *ZoomController) ApplyZoomForURL(url string) {
107	if url == "" {
108		return
109	}
110	currentDomain := services.ZoomKeyForLog(url)
111	z.loadZoomLevelAsync(url, currentDomain, false)
112}
113
114// ApplyZoomForURLWithLevel applies a known zoom level without hitting the database again.
115func (z *ZoomController) ApplyZoomForURLWithLevel(url string, zoomLevel float64, allowToast bool) {
116	if z == nil || url == "" {
117		return
118	}
119	z.applyZoomLevel(url, services.ZoomKeyForLog(url), zoomLevel, allowToast)
120}
121
122func (z *ZoomController) loadZoomLevelAsync(url, domain string, allowToast bool) {
123	if z.browserService == nil || z.webView == nil || url == "" {
124		return
125	}
126	go func(url, domain string, allowToast bool) {
127		ctx := context.Background()
128		zoomLevel, err := z.browserService.GetZoomLevel(ctx, url)
129		if err != nil {
130			return
131		}
132
133		z.applyZoomLevel(url, domain, zoomLevel, allowToast)
134	}(url, domain, allowToast)
135}
136
137func (z *ZoomController) applyZoomLevel(url, domain string, zoomLevel float64, allowToast bool) {
138	if z == nil || z.webView == nil || url == "" {
139		return
140	}
141
142	z.webView.RunOnMainThread(func() {
143		if z.webView == nil {
144			return
145		}
146		if z.currentURL != url {
147			z.currentURL = url
148		}
149
150		// Clear any existing timer
151		if z.programmaticTimer != nil {
152			z.programmaticTimer.Stop()
153		}
154
155		z.programmaticChange = true
156
157		// Reset programmatic change flag after a delay to handle async zoom events
158		z.programmaticTimer = time.AfterFunc(200*time.Millisecond, func() {
159			z.programmaticChange = false
160			z.programmaticTimer = nil
161		})
162
163		if err := z.webView.SetZoom(zoomLevel); err != nil {
164			log.Printf("Warning: failed to set zoom: %v", err)
165			return
166		}
167
168		log.Printf("[zoom] loaded %.2f for %s", zoomLevel, domain)
169		if allowToast && domain != z.lastZoomDomain && z.lastZoomDomain != "" {
170			z.showZoomToast(zoomLevel)
171		}
172		z.lastZoomDomain = domain
173	})
174}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close