browser_window_history_sidebar.go RawHistoryFolder
1package ui
2
3import (
4 "context"
5 "fmt"
6
7 "github.com/bnema/dumber/internal/application/port"
8 "github.com/bnema/dumber/internal/application/usecase"
9 "github.com/bnema/dumber/internal/logging"
10 "github.com/bnema/dumber/internal/ui/component"
11 "github.com/bnema/dumber/internal/ui/window"
12)
13
14// initHistorySidebar creates and mounts the history sidebar into the
15// browser window's sidebar container. The sidebar is hidden by default.
16func (bw *browserWindow) initHistorySidebar(ctx context.Context, a *App) {
17 if bw == nil || a == nil || bw.mainWindow == nil || a.deps == nil || a.deps.HistoryUC == nil {
18 return
19 }
20 log := logging.FromContext(ctx)
21
22 cfg := a.buildHistorySidebarConfig(bw)
23
24 sidebar := component.NewHistorySidebar(ctx, cfg)
25 if sidebar == nil {
26 log.Warn().Msg("failed to create history sidebar")
27 return
28 }
29
30 bw.historySidebar = sidebar
31
32 // Mount into the main window's sidebar box.
33 bw.mainWindow.SetSidebarWidget(sidebar.Widget())
34 bw.historySidebar.Hide()
35 bw.mainWindow.SetSidebarVisible(false)
36 bw.sidebarVisible = false
37 bw.activeSidebarKind = nativeSidebarNone
38
39 // Apply sidebar width from config, falling back to the default 320px.
40 // The width is clamped to [280, 380] by SetSidebarWidth internally.
41 bw.applySidebarWidthConfig(a)
42
43 log.Debug().Msg("history sidebar initialized")
44}
45
46// buildHistorySidebarConfig constructs the HistorySidebarConfig for the given
47// browser window. Extracted from initHistorySidebar for testability.
48//
49//nolint:dupl // History and favorites sidebar configs intentionally mirror each other for separate component types.
50func (a *App) buildHistorySidebarConfig(bw *browserWindow) component.HistorySidebarConfig {
51 var historyUC port.HistorySidebarHistory
52 if a.deps != nil {
53 historyUC = a.deps.HistoryUC
54 }
55
56 return component.HistorySidebarConfig{
57 HistoryUC: historyUC,
58 OnNavigate: func(navCtx context.Context, url string) error {
59 return a.navigateHistorySidebarSelection(navCtx, bw, url)
60 },
61 OnNavigateKeepOpen: func(navCtx context.Context, url string) error {
62 return a.navigateHistorySidebarSelection(navCtx, bw, url)
63 },
64 OnOpenInNewPane: func(splitCtx context.Context, url string) error {
65 if a.wsCoord == nil || !a.hasBrowserWindow(bw) {
66 return nil
67 }
68 a.activateBrowserWindow(bw)
69 return a.wsCoord.SplitWithURL(splitCtx, usecase.SplitRight, url)
70 },
71 OnClose: func() {
72 a.hideAndRestoreFocusForBrowserWindow(bw)
73 },
74 }
75}
76
77func (a *App) navigateHistorySidebarSelection(ctx context.Context, bw *browserWindow, url string) error {
78 if a == nil || bw == nil || !a.hasBrowserWindow(bw) {
79 return nil
80 }
81 return a.navigateFromBrowserWindow(ctx, bw, url)
82}
83
84// toggleHistorySidebar toggles sidebar visibility.
85func (bw *browserWindow) toggleHistorySidebar() {
86 if bw == nil || bw.historySidebar == nil {
87 return
88 }
89
90 if bw.sidebarVisible && bw.activeSidebarKind == nativeSidebarHistory {
91 bw.hideHistorySidebar()
92 } else {
93 bw.showHistorySidebar()
94 }
95}
96
97// showHistorySidebar makes the sidebar visible and grabs focus for the search
98// entry.
99func (bw *browserWindow) showHistorySidebar() {
100 if bw == nil || bw.historySidebar == nil || bw.mainWindow == nil {
101 return
102 }
103 bw.mountNativeSidebar(nativeSidebarHistory)
104 bw.historySidebar.Show()
105 bw.mainWindow.SetSidebarVisible(true)
106 bw.sidebarVisible = true
107 bw.activeSidebarKind = nativeSidebarHistory
108}
109
110// hideHistorySidebar hides the sidebar. Callers should also restore focus
111// to the active content pane after calling this.
112func (bw *browserWindow) hideHistorySidebar() {
113 if bw == nil || bw.historySidebar == nil || bw.mainWindow == nil {
114 return
115 }
116 bw.historySidebar.Hide()
117 bw.mainWindow.SetSidebarVisible(false)
118 bw.sidebarVisible = false
119 if bw.activeSidebarKind == nativeSidebarHistory {
120 bw.activeSidebarKind = nativeSidebarNone
121 }
122}
123
124func (bw *browserWindow) mountNativeSidebar(kind nativeSidebarKind) {
125 if bw == nil || bw.mainWindow == nil || bw.activeSidebarKind == kind {
126 return
127 }
128 if bw.historySidebar != nil {
129 bw.historySidebar.Hide()
130 }
131 if bw.favoritesSidebar != nil {
132 bw.favoritesSidebar.Hide()
133 }
134 switch kind {
135 case nativeSidebarHistory:
136 if bw.historySidebar != nil {
137 bw.mainWindow.SetSidebarWidget(bw.historySidebar.Widget())
138 }
139 case nativeSidebarFavorites:
140 if bw.favoritesSidebar != nil {
141 bw.mainWindow.SetSidebarWidget(bw.favoritesSidebar.Widget())
142 }
143 }
144}
145
146// historySidebarWidthConfig extracts the config-backed sidebar width and
147// applies it via the MainWindow.SetSidebarWidth path. It is called during
148// initialization and can be reused if config is reloaded at runtime.
149func historySidebarWidthConfig(widthPx int) window.SidebarWidthConfig {
150 cfg := window.SidebarDefaultWidth()
151 if widthPx > 0 {
152 cfg.WidthPx = widthPx
153 }
154 return cfg
155}
156
157// applySidebarWidthConfig applies the config-backed sidebar width to the
158// main window's sidebar.
159func (bw *browserWindow) applySidebarWidthConfig(a *App) {
160 if bw == nil || bw.mainWindow == nil || a == nil {
161 return
162 }
163 bw.mainWindow.SetSidebarWidth(historySidebarWidthConfig(a.runtimeConfigSnapshot().UI.SidebarWidth))
164}
165
166// toggleHistorySidebarAction is the keyboard-action handler for toggling the
167// history sidebar on the last focused browser window.
168func (a *App) toggleHistorySidebarAction(ctx context.Context) error {
169 bw := a.lastFocusedBrowserWindow()
170 if bw == nil {
171 return fmt.Errorf("history sidebar unavailable: no focused browser window")
172 }
173 if bw.historySidebar == nil {
174 return fmt.Errorf("history sidebar unavailable: native sidebar not initialized")
175 }
176 if bw.sidebarVisible && bw.activeSidebarKind == nativeSidebarHistory {
177 a.hideAndRestoreFocusForBrowserWindow(bw)
178 return nil
179 }
180 bw.toggleHistorySidebar()
181 return nil
182}