1package ui
2
3import (
4 "context"
5
6 "github.com/bnema/dumber/internal/domain/entity"
7 domainurl "github.com/bnema/dumber/internal/domain/url"
8 "github.com/bnema/dumber/internal/logging"
9)
10
11// openRelayURLOnMainThread opens a relayed external URL, reusing the pane from
12// a recent identical request when it still exists. Must run on the main thread.
13func (a *App) openRelayURLOnMainThread(ctx context.Context, url string, cfg entity.RuntimeConfigSnapshot) error {
14 if a.reuseRelayPane(ctx, url) {
15 return nil
16 }
17 if err := a.openExternalURLOnMainThread(ctx, url, cfg); err != nil {
18 return err
19 }
20 a.recordRelayPane(url)
21 return nil
22}
23
24// reuseRelayPane navigates and focuses the pane previously opened for url.
25// It reports false when there is nothing reusable and a new pane should be created.
26func (a *App) reuseRelayPane(ctx context.Context, url string) bool {
27 paneID, ok := a.externalRelayPanes.Lookup(url, func(id entity.PaneID) bool {
28 return a.browserWindowForPane(id) != nil
29 })
30 if !ok {
31 return false
32 }
33 log := logging.FromContext(ctx)
34
35 bw := a.browserWindowForPane(paneID)
36 var tab *entity.Tab
37 if bw != nil && bw.tabs != nil {
38 for _, candidate := range bw.tabs.Tabs {
39 if candidate != nil && candidate.Workspace != nil && candidate.Workspace.FindPane(paneID) != nil {
40 tab = candidate
41 break
42 }
43 }
44 }
45 if tab == nil || a.navCoord == nil || a.contentCoord == nil {
46 a.externalRelayPanes.Forget(url)
47 return false
48 }
49
50 // Reload the URL first: when the pane cannot be navigated, fall back to
51 // creating a fresh pane instead of focusing a dead one.
52 if err := a.navCoord.NavigateWebView(ctx, url, paneID, a.contentCoord.GetWebView(paneID)); err != nil {
53 log.Warn().Err(err).
54 Str("pane_id", string(paneID)).
55 Str("url_host", logging.SafeURLHost(url)).
56 Msg("ui: external URL relay pane could not be navigated; opening a new pane")
57 a.externalRelayPanes.Forget(url)
58 return false
59 }
60
61 a.focusRelayPane(ctx, bw, tab, paneID)
62 log.Debug().
63 Str("pane_id", string(paneID)).
64 Str("url_host", logging.SafeURLHost(url)).
65 Msg("ui: external URL reused relay pane")
66 return true
67}
68
69func (a *App) focusRelayPane(ctx context.Context, bw *browserWindow, tab *entity.Tab, paneID entity.PaneID) {
70 if bw.tabs.ActiveTabID != tab.ID {
71 if a.tabCoord != nil {
72 if err := a.tabCoord.Switch(ctx, a.tabTargetForBrowserWindow(bw), tab.ID); err != nil {
73 logging.FromContext(ctx).Warn().Err(err).
74 Str("tab_id", string(tab.ID)).
75 Msg("ui: failed to switch to relay pane tab")
76 }
77 } else {
78 bw.tabs.SetActive(tab.ID)
79 if bw.mainWindow != nil && bw.mainWindow.TabBar() != nil {
80 bw.mainWindow.TabBar().SetActive(tab.ID)
81 }
82 a.switchWorkspaceView(ctx, tab.ID)
83 }
84 }
85
86 wsView := a.workspaceViews[tab.ID]
87 if a.wsCoord != nil {
88 a.wsCoord.FocusPaneByID(ctx, tab.Workspace, wsView, paneID)
89 } else {
90 tab.Workspace.ActivePaneID = paneID
91 }
92
93 // A floating pane over the tab would hide the relay pane and keep the
94 // active-pane override pointing at itself.
95 a.hideVisibleFloatingSessions(ctx, tab.ID, nil)
96
97 if bw.mainWindow != nil {
98 bw.mainWindow.Show()
99 }
100 a.activateBrowserWindow(bw)
101 a.syncFloatingFocus()
102 if wsView != nil {
103 wsView.FocusPane(paneID)
104 }
105}
106
107// recordRelayPane remembers the pane that was just created for url. After a
108// successful placement the new pane is the active pane of the last-focused
109// window's active tab (tabbed, split, stacked, and fresh window alike).
110func (a *App) recordRelayPane(url string) {
111 ws := a.activeWorkspaceForBrowserWindow(a.lastFocusedBrowserWindow())
112 if ws == nil {
113 return
114 }
115 active := ws.ActivePane()
116 if active == nil || active.Pane == nil || domainurl.RelayURLKey(active.Pane.URI) != domainurl.RelayURLKey(url) {
117 return
118 }
119 a.externalRelayPanes.Record(url, active.Pane.ID)
120}