From ae3f5b10886a160ebe6c3370e0357d922ef49568 Mon Sep 17 00:00:00 2001 From: Zoltan Papp Date: Sat, 13 Jun 2026 00:37:00 +0200 Subject: [PATCH] Reorder UI service files to follow member-ordering convention Group all type declarations at the top, keep each type's methods with it, move package-level helpers and static functions to the end of the file. - connection.go: ClientError methods sit directly under the struct; the classifyDaemonError / translateShort helpers move to the file end. - windowmanager.go: the title / retitleAll / hideOtherWindowsLocked / restoreHiddenWindowsLocked / getScreenBasedOnCursorPosition helpers and the errorDialogURL / u32ptr static functions move to the file end; u32ptr no longer splits the const/var block. --- client/ui/services/connection.go | 120 +++++++-------- client/ui/services/windowmanager.go | 219 ++++++++++++++-------------- 2 files changed, 170 insertions(+), 169 deletions(-) diff --git a/client/ui/services/connection.go b/client/ui/services/connection.go index 6048d630d..4150992c7 100644 --- a/client/ui/services/connection.go +++ b/client/ui/services/connection.go @@ -58,66 +58,6 @@ func (e *ClientError) MarshalJSON() ([]byte, error) { return json.Marshal((*alias)(e)) } -// classifyDaemonError maps a gRPC error to a ClientError by matching known -// substrings to a stable code. A missing locale entry surfaces as a visible -// "error." string — a deliberate fail-loud signal to update the bundle. -func (s *Connection) classifyDaemonError(err error) *ClientError { - if err == nil { - return nil - } - - msg := err.Error() - if st, ok := gstatus.FromError(err); ok { - msg = st.Message() - } - lower := strings.ToLower(msg) - - code := "unknown" - switch { - case strings.Contains(lower, "token used before issued"), - strings.Contains(lower, "token is not valid yet"): - code = "jwt_clock_skew" - case strings.Contains(lower, "token is expired"), - strings.Contains(lower, "token has expired"): - code = "jwt_expired" - case strings.Contains(lower, "token signature is invalid"): - code = "jwt_signature_invalid" - case strings.Contains(lower, "peer login has expired"): - code = "session_expired" - case strings.Contains(lower, "invalid setup-key"), - strings.Contains(lower, "invalid setup key"): - code = "invalid_setup_key" - case strings.Contains(lower, "permission denied"): - code = "permission_denied" - case strings.Contains(lower, "no connection could be made"), - strings.Contains(lower, "connection refused"), - strings.Contains(lower, "context deadline exceeded"): - code = "daemon_unreachable" - } - - return &ClientError{ - Code: code, - Short: s.translateShort(code), - Long: msg, - } -} - -// translateShort resolves the localised short message for code, returning the -// bare "error." key when no translation is available so the gap stays visible. -func (s *Connection) translateShort(code string) string { - key := "error." + code - if s.translator == nil { - return key - } - lang := i18n.DefaultLanguage - if s.prefs != nil { - if pref := s.prefs.Get().Language; pref != "" { - lang = pref - } - } - return s.translator.Translate(lang, key) -} - // LoginParams are the inputs to Login. type LoginParams struct { ProfileName string `json:"profileName"` @@ -317,3 +257,63 @@ func (s *Connection) Logout(ctx context.Context, p LogoutParams) error { return nil } + +// classifyDaemonError maps a gRPC error to a ClientError by matching known +// substrings to a stable code. A missing locale entry surfaces as a visible +// "error." string — a deliberate fail-loud signal to update the bundle. +func (s *Connection) classifyDaemonError(err error) *ClientError { + if err == nil { + return nil + } + + msg := err.Error() + if st, ok := gstatus.FromError(err); ok { + msg = st.Message() + } + lower := strings.ToLower(msg) + + code := "unknown" + switch { + case strings.Contains(lower, "token used before issued"), + strings.Contains(lower, "token is not valid yet"): + code = "jwt_clock_skew" + case strings.Contains(lower, "token is expired"), + strings.Contains(lower, "token has expired"): + code = "jwt_expired" + case strings.Contains(lower, "token signature is invalid"): + code = "jwt_signature_invalid" + case strings.Contains(lower, "peer login has expired"): + code = "session_expired" + case strings.Contains(lower, "invalid setup-key"), + strings.Contains(lower, "invalid setup key"): + code = "invalid_setup_key" + case strings.Contains(lower, "permission denied"): + code = "permission_denied" + case strings.Contains(lower, "no connection could be made"), + strings.Contains(lower, "connection refused"), + strings.Contains(lower, "context deadline exceeded"): + code = "daemon_unreachable" + } + + return &ClientError{ + Code: code, + Short: s.translateShort(code), + Long: msg, + } +} + +// translateShort resolves the localised short message for code, returning the +// bare "error." key when no translation is available so the gap stays visible. +func (s *Connection) translateShort(code string) string { + key := "error." + code + if s.translator == nil { + return key + } + lang := i18n.DefaultLanguage + if s.prefs != nil { + if pref := s.prefs.Get().Language; pref != "" { + lang = pref + } + } + return s.translator.Translate(lang, key) +} diff --git a/client/ui/services/windowmanager.go b/client/ui/services/windowmanager.go index 02cc731af..04784e190 100644 --- a/client/ui/services/windowmanager.go +++ b/client/ui/services/windowmanager.go @@ -44,8 +44,6 @@ const WindowHeight = 660 // Wails reads CustomTheme colours as 0x00BBGGRR (RGB byte order reversed). // Border/title bar match AppRightPanel bg-nb-gray-940 (#1C1E21); title text // matches text-nb-gray-100 (#E4E7E9). -func u32ptr(v uint32) *uint32 { return &v } - var microsoftWindowsTheme = &application.WindowTheme{ BorderColour: u32ptr(0x00211E1C), TitleBarColour: u32ptr(0x00211E1C), @@ -144,21 +142,6 @@ type WindowManager struct { recenterOnShow func() bool } -// title resolves a window-title i18n key in the user's current language. -// Falls back to the raw key when translator or prefs are missing. -func (s *WindowManager) title(key string) string { - if s.translator == nil { - return key - } - lang := i18n.DefaultLanguage - if s.prefs != nil { - if pref := s.prefs.Get().Language; pref != "" { - lang = pref - } - } - return s.translator.Translate(lang, key) -} - // NewWindowManager wires the manager to the main app. translator and prefs may // be nil (tests), in which case title() falls back to the raw i18n key. // @@ -210,32 +193,6 @@ func NewWindowManager(app *application.App, mainWindow *application.WebviewWindo return s } -// retitleAll re-applies the localised title to every alive auxiliary window. -// Snapshots the window pointers under s.mu so a concurrent Open*/Close* can't -// race; SetTitle dispatches to the OS UI thread, so the calls are safe to make -// after releasing the lock. -func (s *WindowManager) retitleAll() { - s.mu.Lock() - type pair struct { - win *application.WebviewWindow - key string - } - wins := []pair{ - {s.settings, "window.title.settings"}, - {s.browserLogin, "window.title.signIn"}, - {s.sessionExpiration, "window.title.sessionExpiration"}, - {s.installProgress, "window.title.updating"}, - {s.welcome, "window.title.welcome"}, - {s.errorDialog, "window.title.error"}, - } - s.mu.Unlock() - for _, p := range wins { - if p.win != nil { - p.win.SetTitle(s.title(p.key)) - } - } -} - // OpenSettings shows the settings window on tab (empty → General). // // The window keeps a single URL (/#/settings) for its lifetime: SetURL per open @@ -305,33 +262,6 @@ func (s *WindowManager) OpenBrowserLogin(uri string) { s.centerWhenReady(s.browserLogin) } -// hideOtherWindowsLocked hides every visible window except keepName, recording -// them in hiddenForLogin for restoreHiddenWindowsLocked. Caller must hold s.mu. -func (s *WindowManager) hideOtherWindowsLocked(keepName string) { - for _, w := range s.app.Window.GetAll() { - if w == nil || w.Name() == keepName { - continue - } - if !w.IsVisible() { - continue - } - w.Hide() - s.hiddenForLogin = append(s.hiddenForLogin, w) - } -} - -// restoreHiddenWindowsLocked re-shows windows hidden by -// hideOtherWindowsLocked. Caller must hold s.mu. -func (s *WindowManager) restoreHiddenWindowsLocked() { - for _, w := range s.hiddenForLogin { - if w == nil { - continue - } - w.Show() - } - s.hiddenForLogin = nil -} - // BrowserLoginWindow returns the live SSO popup, or nil if no SSO flow is in // progress. While non-nil it is the app's focal window: tray "Open" and // dock/taskbar activation hand off to it instead of the main window. @@ -508,24 +438,6 @@ func (s *WindowManager) OpenError(title, message string) { s.centerWhenReady(s.errorDialog) } -// errorDialogURL builds the error window's hash-route start URL with title and -// message as query params, escaped so newlines and ampersands common in -// formatted daemon errors survive into useSearchParams. -func errorDialogURL(title, message string) string { - q := url.Values{} - if title != "" { - q.Set("title", title) - } - if message != "" { - q.Set("message", message) - } - startURL := "/#/dialog/error" - if enc := q.Encode(); enc != "" { - startURL += "?" + enc - } - return startURL -} - func (s *WindowManager) CloseError() { s.mu.Lock() w := s.errorDialog @@ -564,27 +476,6 @@ func (s *WindowManager) SetRecenterOnShow(pred func() bool) { s.recenterOnShow = pred } -// getScreenBasedOnCursorPosition returns the display the OS cursor is on, -// falling back to the main-window screen, then nil (OS-default placement). -// On Linux the cursor query uses XQueryPointer, which works on Wayland via -// XWayland. -func (s *WindowManager) getScreenBasedOnCursorPosition() *application.Screen { - if s.app == nil || s.app.Screen == nil { - return nil - } - if p, ok := getCursorPosition(s.app); ok { - if sc := s.app.Screen.ScreenNearestDipPoint(p); sc != nil { - return sc - } - } - if s.mainWindow != nil { - if sc, err := s.mainWindow.GetScreen(); err == nil { - return sc - } - } - return nil -} - // centerWhenReady centers w once its native window exists, but only where the // WM won't (recenterOnShow); otherwise it returns immediately so it never fights // a user-moved window. @@ -648,3 +539,113 @@ func (s *WindowManager) centerOnCursorScreen(w *application.WebviewWindow) { } }() } + +// title resolves a window-title i18n key in the user's current language. +// Falls back to the raw key when translator or prefs are missing. +func (s *WindowManager) title(key string) string { + if s.translator == nil { + return key + } + lang := i18n.DefaultLanguage + if s.prefs != nil { + if pref := s.prefs.Get().Language; pref != "" { + lang = pref + } + } + return s.translator.Translate(lang, key) +} + +// retitleAll re-applies the localised title to every alive auxiliary window. +// Snapshots the window pointers under s.mu so a concurrent Open*/Close* can't +// race; SetTitle dispatches to the OS UI thread, so the calls are safe to make +// after releasing the lock. +func (s *WindowManager) retitleAll() { + s.mu.Lock() + type pair struct { + win *application.WebviewWindow + key string + } + wins := []pair{ + {s.settings, "window.title.settings"}, + {s.browserLogin, "window.title.signIn"}, + {s.sessionExpiration, "window.title.sessionExpiration"}, + {s.installProgress, "window.title.updating"}, + {s.welcome, "window.title.welcome"}, + {s.errorDialog, "window.title.error"}, + } + s.mu.Unlock() + for _, p := range wins { + if p.win != nil { + p.win.SetTitle(s.title(p.key)) + } + } +} + +// hideOtherWindowsLocked hides every visible window except keepName, recording +// them in hiddenForLogin for restoreHiddenWindowsLocked. Caller must hold s.mu. +func (s *WindowManager) hideOtherWindowsLocked(keepName string) { + for _, w := range s.app.Window.GetAll() { + if w == nil || w.Name() == keepName { + continue + } + if !w.IsVisible() { + continue + } + w.Hide() + s.hiddenForLogin = append(s.hiddenForLogin, w) + } +} + +// restoreHiddenWindowsLocked re-shows windows hidden by +// hideOtherWindowsLocked. Caller must hold s.mu. +func (s *WindowManager) restoreHiddenWindowsLocked() { + for _, w := range s.hiddenForLogin { + if w == nil { + continue + } + w.Show() + } + s.hiddenForLogin = nil +} + +// getScreenBasedOnCursorPosition returns the display the OS cursor is on, +// falling back to the main-window screen, then nil (OS-default placement). +// On Linux the cursor query uses XQueryPointer, which works on Wayland via +// XWayland. +func (s *WindowManager) getScreenBasedOnCursorPosition() *application.Screen { + if s.app == nil || s.app.Screen == nil { + return nil + } + if p, ok := getCursorPosition(s.app); ok { + if sc := s.app.Screen.ScreenNearestDipPoint(p); sc != nil { + return sc + } + } + if s.mainWindow != nil { + if sc, err := s.mainWindow.GetScreen(); err == nil { + return sc + } + } + return nil +} + +// errorDialogURL builds the error window's hash-route start URL with title and +// message as query params, escaped so newlines and ampersands common in +// formatted daemon errors survive into useSearchParams. +func errorDialogURL(title, message string) string { + q := url.Values{} + if title != "" { + q.Set("title", title) + } + if message != "" { + q.Set("message", message) + } + startURL := "/#/dialog/error" + if enc := q.Encode(); enc != "" { + startURL += "?" + enc + } + return startURL +} + +// u32ptr returns a pointer to v, for the optional *uint32 Wails theme fields. +func u32ptr(v uint32) *uint32 { return &v }