From 8b4f3f91be3a9b15f0a92460142cad88b5466825 Mon Sep 17 00:00:00 2001 From: Joe Julian Date: Mon, 6 Apr 2026 17:29:23 -0700 Subject: [PATCH] Add direct remote sync shortcut --- main.go | 116 +++++++++++++++++++++++++++++++-------------------- main_test.go | 46 ++++++++++++++++++++ 2 files changed, 116 insertions(+), 46 deletions(-) diff --git a/main.go b/main.go index 9e012d0..ce4615c 100644 --- a/main.go +++ b/main.go @@ -2283,6 +2283,18 @@ func (u *ui) openSelectedVaultRemoteButtonLabel() string { return "Open Saved Remote" } +func (u *ui) shouldShowDirectRemoteSyncShortcut() bool { + if !u.hasOpenVault() || u.isVaultLocked() || u.state.Section != appstate.SectionEntries { + return false + } + _, ok := u.selectedVaultRemoteBinding() + return ok +} + +func (u *ui) directRemoteSyncShortcutLabel() string { + return "Use Remote Sync" +} + func remoteBindingSuffix(baseURL, path, username string) string { sum := sha256.Sum256([]byte(strings.TrimSpace(baseURL) + "\n" + strings.TrimSpace(path) + "\n" + strings.TrimSpace(username))) return hex.EncodeToString(sum[:8]) @@ -6244,56 +6256,68 @@ func (u *ui) pathBar(gtx layout.Context) layout.Dimensions { pathSource = append([]string{}, u.currentPath...) } crumbs, indices := u.visibleBreadcrumbs(pathSource) - return layout.Flex{Alignment: layout.Middle}.Layout(gtx, func() []layout.FlexChild { - children := make([]layout.FlexChild, 0, len(crumbs)*2) - for i, name := range crumbs { - index := i - label := name - children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions { - for u.breadcrumbs[index].Clicked(gtx) { - target := indices[index] - if target == 0 { - root := u.hiddenVaultRoot() - if root == "" { - u.setCurrentPath(nil) - } else { - u.setCurrentPath([]string{root}) - } - } else { - nextPath := pathSource[:target] - root := u.hiddenVaultRoot() - if root != "" { - nextPath = append([]string{root}, nextPath...) - } - u.setCurrentPath(nextPath) - } - u.filter() - } - btn := material.Button(u.theme, &u.breadcrumbs[index], label) - btn.Background, btn.Color = buttonFocusColors(u.accessibilityPrefs, u.isFocused(breadcrumbFocusID(index))) - btn.TextSize = unit.Sp(11) - if u.mode == "phone" { - btn.TextSize = unit.Sp(9) - btn.Inset = layout.Inset{Top: 3, Bottom: 3, Left: 6, Right: 6} - } else { - btn.Inset = layout.Inset{Top: 5, Bottom: 5, Left: 9, Right: 9} - } - return btn.Layout(gtx) - })) - if i < len(crumbs)-1 { + crumbBar := func(gtx layout.Context) layout.Dimensions { + return layout.Flex{Alignment: layout.Middle}.Layout(gtx, func() []layout.FlexChild { + children := make([]layout.FlexChild, 0, len(crumbs)*2) + for i, name := range crumbs { + index := i + label := name children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions { - lbl := material.Label(u.theme, unit.Sp(12), "/") - lbl.Color = mutedColor - inset := unit.Dp(6) - if u.mode == "phone" { - inset = unit.Dp(4) + for u.breadcrumbs[index].Clicked(gtx) { + target := indices[index] + if target == 0 { + root := u.hiddenVaultRoot() + if root == "" { + u.setCurrentPath(nil) + } else { + u.setCurrentPath([]string{root}) + } + } else { + nextPath := pathSource[:target] + root := u.hiddenVaultRoot() + if root != "" { + nextPath = append([]string{root}, nextPath...) + } + u.setCurrentPath(nextPath) + } + u.filter() } - return layout.UniformInset(inset).Layout(gtx, lbl.Layout) + btn := material.Button(u.theme, &u.breadcrumbs[index], label) + btn.Background, btn.Color = buttonFocusColors(u.accessibilityPrefs, u.isFocused(breadcrumbFocusID(index))) + btn.TextSize = unit.Sp(11) + if u.mode == "phone" { + btn.TextSize = unit.Sp(9) + btn.Inset = layout.Inset{Top: 3, Bottom: 3, Left: 6, Right: 6} + } else { + btn.Inset = layout.Inset{Top: 5, Bottom: 5, Left: 9, Right: 9} + } + return btn.Layout(gtx) })) + if i < len(crumbs)-1 { + children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions { + lbl := material.Label(u.theme, unit.Sp(12), "/") + lbl.Color = mutedColor + inset := unit.Dp(6) + if u.mode == "phone" { + inset = unit.Dp(4) + } + return layout.UniformInset(inset).Layout(gtx, lbl.Layout) + })) + } } - } - return children - }()...) + return children + }()...) + } + if !u.shouldShowDirectRemoteSyncShortcut() { + return crumbBar(gtx) + } + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, + layout.Rigid(crumbBar), + layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return tonedButton(gtx, u.theme, &u.openSelectedVaultRemote, u.directRemoteSyncShortcutLabel()) + }), + ) } func (u *ui) visibleBreadcrumbs(displayPath []string) ([]string, []int) { diff --git a/main_test.go b/main_test.go index 8d9c88f..eccb42f 100644 --- a/main_test.go +++ b/main_test.go @@ -5422,6 +5422,52 @@ func TestUIOpenSelectedVaultRemoteButtonLabelUsesSavedRemoteLanguageForMultipleC } } +func TestUIShouldShowDirectRemoteSyncShortcutForSavedBinding(t *testing.T) { + t.Parallel() + + u := newUIWithModel("desktop", vault.Model{ + Entries: []vault.Entry{{ + ID: "remote-creds-1", + Title: "WebDAV Sign-In", + Username: "tjulian", + Path: []string{"Crew", "Internet"}, + }}, + RemoteProfiles: []vault.RemoteProfile{{ + ID: "family-webdav", + Name: "Family Vault", + Backend: vault.RemoteBackendWebDAV, + BaseURL: "https://dav.example.invalid/remote.php/dav", + Path: "files/family/keepass.kdbx", + }}, + }) + u.state.Section = appstate.SectionEntries + + if !u.shouldShowDirectRemoteSyncShortcut() { + t.Fatal("shouldShowDirectRemoteSyncShortcut() = false, want true for an opened vault with a saved remote binding") + } +} + +func TestUIShouldHideDirectRemoteSyncShortcutWithoutSavedBinding(t *testing.T) { + t.Parallel() + + u := newUIWithModel("desktop", vault.Model{}) + u.state.Section = appstate.SectionEntries + + if u.shouldShowDirectRemoteSyncShortcut() { + t.Fatal("shouldShowDirectRemoteSyncShortcut() = true, want false without a saved remote binding") + } +} + +func TestUIDirectRemoteSyncShortcutLabelUsesSyncLanguage(t *testing.T) { + t.Parallel() + + u := newUIWithSession("desktop", &session.Manager{}) + + if got := u.directRemoteSyncShortcutLabel(); got != "Use Remote Sync" { + t.Fatalf("directRemoteSyncShortcutLabel() = %q, want Use Remote Sync", got) + } +} + func TestUISaveCurrentRemoteBindingActionPersistsBindingIntoVault(t *testing.T) { t.Parallel()