v0.23.2 bfd5eb4

manage_zoom.go RawHistoryFolder

1// Package usecase contains application use cases that orchestrate domain logic.
2package usecase
3
4import (
5	"context"
6	"fmt"
7	"net/url"
8
9	"github.com/bnema/dumber/internal/application/port"
10	"github.com/bnema/dumber/internal/domain/entity"
11	"github.com/bnema/dumber/internal/domain/repository"
12	"github.com/bnema/dumber/internal/logging"
13)
14
15// ManageZoomUseCase handles per-domain zoom level operations.
16// It uses an LRU cache to avoid database queries on every navigation.
17type ManageZoomUseCase struct {
18	zoomRepo    repository.ZoomRepository
19	defaultZoom float64
20	cache       port.Cache[string, *entity.ZoomLevel]
21}
22
23// NewManageZoomUseCase creates a new zoom management use case.
24// defaultZoom is the zoom level to use when resetting (typically from config).
25// cache is an LRU cache for zoom levels to avoid database queries on repeat visits.
26func NewManageZoomUseCase(
27	zoomRepo repository.ZoomRepository,
28	defaultZoom float64,
29	cache port.Cache[string, *entity.ZoomLevel],
30) *ManageZoomUseCase {
31	if defaultZoom <= 0 {
32		defaultZoom = entity.ZoomDefault
33	}
34	return &ManageZoomUseCase{
35		zoomRepo:    zoomRepo,
36		defaultZoom: defaultZoom,
37		cache:       cache,
38	}
39}
40
41// DefaultZoom returns the configured default zoom level.
42func (uc *ManageZoomUseCase) DefaultZoom() float64 {
43	return uc.defaultZoom
44}
45
46// GetZoom retrieves the zoom level for a domain.
47// Returns the configured default zoom level if none is set.
48// Uses LRU cache to avoid database queries on repeat visits.
49func (uc *ManageZoomUseCase) GetZoom(ctx context.Context, domain string) (*entity.ZoomLevel, error) {
50	log := logging.FromContext(ctx)
51
52	// Check cache first (fast path - no I/O)
53	if uc.cache != nil {
54		if cached, ok := uc.cache.Get(domain); ok {
55			log.Debug().Str("domain", domain).Float64("zoom", cached.ZoomFactor).Msg("zoom level from cache")
56			return cached, nil
57		}
58	}
59
60	// Cache miss - fetch from database
61	log.Debug().Str("domain", domain).Msg("zoom cache miss, querying database")
62	zoom, err := uc.zoomRepo.Get(ctx, domain)
63	if err != nil {
64		return nil, fmt.Errorf("failed to get zoom level: %w", err)
65	}
66
67	if zoom == nil {
68		zoom = entity.NewZoomLevel(domain, uc.defaultZoom)
69		log.Debug().Str("domain", domain).Float64("zoom", zoom.ZoomFactor).Msg("using default zoom")
70	}
71
72	// Cache for future lookups
73	if uc.cache != nil {
74		uc.cache.Set(domain, zoom)
75	}
76
77	return zoom, nil
78}
79
80// SetZoom saves a zoom level for a domain.
81func (uc *ManageZoomUseCase) SetZoom(ctx context.Context, domain string, factor float64) error {
82	log := logging.FromContext(ctx)
83	log.Debug().Str("domain", domain).Float64("factor", factor).Msg("setting zoom level")
84
85	zoom := entity.NewZoomLevel(domain, factor)
86	if err := uc.zoomRepo.Set(ctx, zoom); err != nil {
87		return fmt.Errorf("failed to set zoom level: %w", err)
88	}
89
90	// Update cache
91	if uc.cache != nil {
92		uc.cache.Set(domain, zoom)
93	}
94
95	log.Info().Str("domain", domain).Float64("factor", zoom.ZoomFactor).Msg("zoom level saved")
96	return nil
97}
98
99// ResetZoom removes the custom zoom level for a domain.
100func (uc *ManageZoomUseCase) ResetZoom(ctx context.Context, domain string) error {
101	log := logging.FromContext(ctx)
102	log.Debug().Str("domain", domain).Msg("resetting zoom level")
103
104	if err := uc.zoomRepo.Delete(ctx, domain); err != nil {
105		return fmt.Errorf("failed to reset zoom level: %w", err)
106	}
107
108	// Invalidate cache
109	if uc.cache != nil {
110		uc.cache.Remove(domain)
111	}
112
113	log.Info().Str("domain", domain).Msg("zoom level reset to default")
114	return nil
115}
116
117// ZoomIn increases the zoom level by one step (0.1).
118func (uc *ManageZoomUseCase) ZoomIn(ctx context.Context, domain string, current float64) (*entity.ZoomLevel, error) {
119	log := logging.FromContext(ctx)
120
121	zoom := entity.NewZoomLevel(domain, current)
122	zoom.ZoomIn()
123
124	log.Debug().
125		Str("domain", domain).
126		Float64("from", current).
127		Float64("to", zoom.ZoomFactor).
128		Msg("zooming in")
129
130	if err := uc.zoomRepo.Set(ctx, zoom); err != nil {
131		return nil, fmt.Errorf("failed to save zoom level: %w", err)
132	}
133
134	// Update cache
135	if uc.cache != nil {
136		uc.cache.Set(domain, zoom)
137	}
138
139	return zoom, nil
140}
141
142// ZoomOut decreases the zoom level by one step (0.1).
143func (uc *ManageZoomUseCase) ZoomOut(ctx context.Context, domain string, current float64) (*entity.ZoomLevel, error) {
144	log := logging.FromContext(ctx)
145
146	zoom := entity.NewZoomLevel(domain, current)
147	zoom.ZoomOut()
148
149	log.Debug().
150		Str("domain", domain).
151		Float64("from", current).
152		Float64("to", zoom.ZoomFactor).
153		Msg("zooming out")
154
155	if err := uc.zoomRepo.Set(ctx, zoom); err != nil {
156		return nil, fmt.Errorf("failed to save zoom level: %w", err)
157	}
158
159	// Update cache
160	if uc.cache != nil {
161		uc.cache.Set(domain, zoom)
162	}
163
164	return zoom, nil
165}
166
167// ApplyToWebView loads the saved zoom level and applies it to a webview.
168func (uc *ManageZoomUseCase) ApplyToWebView(ctx context.Context, webview port.WebView, domain string) error {
169	log := logging.FromContext(ctx)
170
171	zoom, err := uc.GetZoom(ctx, domain)
172	if err != nil {
173		return err
174	}
175
176	log.Debug().
177		Str("domain", domain).
178		Float64("factor", zoom.ZoomFactor).
179		Msg("applying zoom to webview")
180
181	if err := webview.SetZoomLevel(ctx, zoom.ZoomFactor); err != nil {
182		return fmt.Errorf("failed to set zoom level: %w", err)
183	}
184	return nil
185}
186
187// GetAll retrieves all saved zoom levels.
188func (uc *ManageZoomUseCase) GetAll(ctx context.Context) ([]*entity.ZoomLevel, error) {
189	log := logging.FromContext(ctx)
190	log.Debug().Msg("getting all zoom levels")
191
192	levels, err := uc.zoomRepo.GetAll(ctx)
193	if err != nil {
194		return nil, fmt.Errorf("failed to get zoom levels: %w", err)
195	}
196
197	log.Debug().Int("count", len(levels)).Msg("retrieved zoom levels")
198	return levels, nil
199}
200
201// ExtractDomain extracts the host from a URL string.
202func ExtractDomain(rawURL string) (string, error) {
203	u, err := url.Parse(rawURL)
204	if err != nil {
205		return "", fmt.Errorf("invalid URL: %w", err)
206	}
207	if u.Host == "" {
208		return "", fmt.Errorf("URL has no host: %s", rawURL)
209	}
210	return u.Host, nil
211}

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