Add saved remote selectors to sync menu

This commit is contained in:
Joe Julian
2026-04-06 15:58:00 -07:00
parent dc03d6a0a5
commit 37297856c4
2 changed files with 178 additions and 2 deletions
+133 -2
View File
@@ -275,6 +275,7 @@ type ui struct {
toggleSyncMenu widget.Clickable toggleSyncMenu widget.Clickable
toggleMainMenu widget.Clickable toggleMainMenu widget.Clickable
openAdvancedSync widget.Clickable openAdvancedSync widget.Clickable
openSelectedVaultRemote widget.Clickable
openSecuritySettings widget.Clickable openSecuritySettings widget.Clickable
openRemotePrefsHelp widget.Clickable openRemotePrefsHelp widget.Clickable
closeAdvancedSync widget.Clickable closeAdvancedSync widget.Clickable
@@ -378,6 +379,8 @@ type ui struct {
groupClicks []widget.Clickable groupClicks []widget.Clickable
recentVaultClicks []widget.Clickable recentVaultClicks []widget.Clickable
recentRemoteClicks []widget.Clickable recentRemoteClicks []widget.Clickable
vaultRemoteProfileClicks []widget.Clickable
vaultRemoteCredentialClicks []widget.Clickable
removeCustomFields []widget.Clickable removeCustomFields []widget.Clickable
state appstate.State state appstate.State
visible []entry visible []entry
@@ -1936,6 +1939,41 @@ func (u *ui) availableRemoteCredentialEntries() []vault.Entry {
return entries return entries
} }
func (u *ui) selectVaultRemoteProfile(id string) {
id = strings.TrimSpace(id)
u.selectedVaultRemoteProfileID = id
for _, profile := range u.availableRemoteProfiles() {
if profile.ID != id {
continue
}
u.remoteBaseURL.SetText(profile.BaseURL)
u.remotePath.SetText(profile.Path)
return
}
}
func (u *ui) selectVaultRemoteCredentialEntry(id string) {
u.selectedVaultRemoteCredentialEntryID = strings.TrimSpace(id)
}
func (u *ui) selectedVaultRemoteProfile() (vault.RemoteProfile, bool) {
for _, profile := range u.availableRemoteProfiles() {
if profile.ID == strings.TrimSpace(u.selectedVaultRemoteProfileID) {
return profile, true
}
}
return vault.RemoteProfile{}, false
}
func (u *ui) selectedVaultRemoteCredentialEntry() (vault.Entry, bool) {
for _, entry := range u.availableRemoteCredentialEntries() {
if entry.ID == strings.TrimSpace(u.selectedVaultRemoteCredentialEntryID) {
return entry, true
}
}
return vault.Entry{}, false
}
func (u *ui) selectedVaultRemoteBinding() (appstate.RemoteBinding, bool) { func (u *ui) selectedVaultRemoteBinding() (appstate.RemoteBinding, bool) {
if strings.TrimSpace(u.selectedVaultRemoteProfileID) == "" || strings.TrimSpace(u.selectedVaultRemoteCredentialEntryID) == "" { if strings.TrimSpace(u.selectedVaultRemoteProfileID) == "" || strings.TrimSpace(u.selectedVaultRemoteCredentialEntryID) == "" {
return appstate.RemoteBinding{}, false return appstate.RemoteBinding{}, false
@@ -3281,6 +3319,28 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
} }
} }
} }
for i := range u.vaultRemoteProfileClicks {
for u.vaultRemoteProfileClicks[i].Clicked(gtx) {
profiles := u.availableRemoteProfiles()
if i < len(profiles) {
u.selectVaultRemoteProfile(profiles[i].ID)
}
}
}
for i := range u.vaultRemoteCredentialClicks {
for u.vaultRemoteCredentialClicks[i].Clicked(gtx) {
entries := u.availableRemoteCredentialEntries()
if i < len(entries) {
u.selectVaultRemoteCredentialEntry(entries[i].ID)
}
}
}
for u.openSelectedVaultRemote.Clicked(gtx) {
if u.lifecycleBusy() {
continue
}
u.startOpenRemoteAction()
}
for u.clearVaultSelection.Clicked(gtx) { for u.clearVaultSelection.Clicked(gtx) {
if u.lifecycleBusy() { if u.lifecycleBusy() {
continue continue
@@ -4389,8 +4449,16 @@ func (u *ui) syncMenuToggle(gtx layout.Context) layout.Dimensions {
} }
func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions { func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions {
profiles := u.availableRemoteProfiles()
credentials := u.availableRemoteCredentialEntries()
if len(u.vaultRemoteProfileClicks) < len(profiles) {
u.vaultRemoteProfileClicks = make([]widget.Clickable, len(profiles))
}
if len(u.vaultRemoteCredentialClicks) < len(credentials) {
u.vaultRemoteCredentialClicks = make([]widget.Clickable, len(credentials))
}
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions { return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, rows := []layout.FlexChild{
layout.Rigid(func(gtx layout.Context) layout.Dimensions { layout.Rigid(func(gtx layout.Context) layout.Dimensions {
lbl := material.Label(u.theme, unit.Sp(11), "Need another source or direction?") lbl := material.Label(u.theme, unit.Sp(11), "Need another source or direction?")
lbl.Color = mutedColor lbl.Color = mutedColor
@@ -4400,7 +4468,70 @@ func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions {
layout.Rigid(func(gtx layout.Context) layout.Dimensions { layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return tonedButton(gtx, u.theme, &u.openAdvancedSync, "Open Advanced Sync") return tonedButton(gtx, u.theme, &u.openAdvancedSync, "Open Advanced Sync")
}), }),
) }
if u.hasOpenVault() && len(profiles) > 0 && len(credentials) > 0 {
rows = append(rows,
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
lbl := material.Label(u.theme, unit.Sp(11), "Use a saved remote profile from this vault")
lbl.Color = mutedColor
return lbl.Layout(gtx)
}),
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
)
for i, profile := range profiles {
i := i
profile := profile
rows = append(rows,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
selected := strings.TrimSpace(u.selectedVaultRemoteProfileID) == profile.ID
return layout.Inset{Bottom: unit.Dp(4)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return recentSelectionCard(gtx, selected, func(gtx layout.Context) layout.Dimensions {
return u.vaultRemoteProfileClicks[i].Layout(gtx, func(gtx layout.Context) layout.Dimensions {
lbl := material.Label(u.theme, unit.Sp(13), profile.Name)
lbl.Color = accentColor
return lbl.Layout(gtx)
})
})
})
}),
)
}
rows = append(rows, layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout))
for i, entry := range credentials {
i := i
entry := entry
rows = append(rows,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
selected := strings.TrimSpace(u.selectedVaultRemoteCredentialEntryID) == entry.ID
label := entry.Title
if strings.TrimSpace(entry.Username) != "" {
label += " · " + strings.TrimSpace(entry.Username)
}
return layout.Inset{Bottom: unit.Dp(4)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return recentSelectionCard(gtx, selected, func(gtx layout.Context) layout.Dimensions {
return u.vaultRemoteCredentialClicks[i].Layout(gtx, func(gtx layout.Context) layout.Dimensions {
lbl := material.Label(u.theme, unit.Sp(13), label)
lbl.Color = accentColor
return lbl.Layout(gtx)
})
})
})
}),
)
}
if _, ok := u.selectedVaultRemoteProfile(); ok {
if _, ok := u.selectedVaultRemoteCredentialEntry(); ok {
rows = append(rows,
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return tonedButton(gtx, u.theme, &u.openSelectedVaultRemote, "Open Saved Remote")
}),
)
}
}
}
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, rows...)
}) })
} }
+45
View File
@@ -4698,6 +4698,51 @@ func TestUIAvailableRemoteProfilesReturnsEmptyWhenLocked(t *testing.T) {
} }
} }
func TestUISelectVaultRemoteProfileUpdatesSelectionAndTargetFields(t *testing.T) {
t.Parallel()
u := newUIWithModel("desktop", vault.Model{
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.selectVaultRemoteProfile("family-webdav")
if got := u.selectedVaultRemoteProfileID; got != "family-webdav" {
t.Fatalf("selectedVaultRemoteProfileID = %q, want family-webdav", got)
}
if got := u.remoteBaseURL.Text(); got != "https://dav.example.invalid/remote.php/dav" {
t.Fatalf("remoteBaseURL = %q, want resolved profile base URL", got)
}
if got := u.remotePath.Text(); got != "files/family/keepass.kdbx" {
t.Fatalf("remotePath = %q, want resolved profile path", got)
}
}
func TestUISelectVaultRemoteCredentialEntryUpdatesSelection(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"},
}},
})
u.selectVaultRemoteCredentialEntry("remote-creds-1")
if got := u.selectedVaultRemoteCredentialEntryID; got != "remote-creds-1" {
t.Fatalf("selectedVaultRemoteCredentialEntryID = %q, want remote-creds-1", got)
}
}
func TestSwitchToLifecycleSelectionResetsLockedLocalSession(t *testing.T) { func TestSwitchToLifecycleSelectionResetsLockedLocalSession(t *testing.T) {
t.Parallel() t.Parallel()