Unify open flow around local vaults
This commit is contained in:
@@ -217,6 +217,13 @@ const (
|
||||
syncDirectionPush syncDirection = "push"
|
||||
)
|
||||
|
||||
type syncDialogPurpose string
|
||||
|
||||
const (
|
||||
syncDialogPurposeAdvanced syncDialogPurpose = "advanced"
|
||||
syncDialogPurposeRemoteSetup syncDialogPurpose = "remote-setup"
|
||||
)
|
||||
|
||||
type ui struct {
|
||||
mode string
|
||||
theme *material.Theme
|
||||
@@ -286,6 +293,7 @@ type ui struct {
|
||||
toggleSyncMenu widget.Clickable
|
||||
toggleMainMenu widget.Clickable
|
||||
openAdvancedSync widget.Clickable
|
||||
useSavedAdvancedSyncRemote widget.Clickable
|
||||
openSelectedVaultRemote widget.Clickable
|
||||
saveCurrentRemoteBinding widget.Clickable
|
||||
openSecuritySettings widget.Clickable
|
||||
@@ -446,6 +454,7 @@ type ui struct {
|
||||
syncRemoteUsername widget.Editor
|
||||
syncRemotePassword widget.Editor
|
||||
selectedSyncRemoteCredentialEntryID string
|
||||
syncDialogPurpose syncDialogPurpose
|
||||
syncDialogOpen bool
|
||||
syncMenuOpen bool
|
||||
mainMenuOpen bool
|
||||
@@ -1361,11 +1370,28 @@ func (u *ui) openAdvancedSyncDialog() {
|
||||
u.syncDialogOpen = true
|
||||
u.syncMenuOpen = false
|
||||
u.showSyncPassword = false
|
||||
u.syncDialogPurpose = syncDialogPurposeAdvanced
|
||||
u.syncSourceMode = u.syncDefaultSourceMode
|
||||
u.syncDirection = u.syncDefaultDirection
|
||||
if strings.TrimSpace(u.syncLocalPath.Text()) == "" {
|
||||
u.syncLocalPath.SetText(strings.TrimSpace(u.vaultPath.Text()))
|
||||
}
|
||||
u.syncSavedRemoteBindingSelection()
|
||||
u.prefillAdvancedSyncRemoteFromSavedBinding()
|
||||
}
|
||||
|
||||
func (u *ui) openRemoteSyncSetupDialog() {
|
||||
u.syncDialogOpen = true
|
||||
u.syncMenuOpen = false
|
||||
u.showSyncPassword = false
|
||||
u.syncDialogPurpose = syncDialogPurposeRemoteSetup
|
||||
u.syncSourceMode = syncSourceRemote
|
||||
u.syncDirection = syncDirectionPush
|
||||
if strings.TrimSpace(u.syncLocalPath.Text()) == "" {
|
||||
u.syncLocalPath.SetText(strings.TrimSpace(u.vaultPath.Text()))
|
||||
}
|
||||
u.syncSavedRemoteBindingSelection()
|
||||
u.prefillAdvancedSyncRemoteFromSavedBinding()
|
||||
}
|
||||
|
||||
func (u *ui) clearSyncLocalImport() {
|
||||
@@ -2012,12 +2038,15 @@ func (u *ui) restoreStartupLifecycleTarget() {
|
||||
remoteRecord, hasRemote, remoteUsedAt := u.latestRecentRemote()
|
||||
|
||||
switch {
|
||||
case hasRemote && (localPath == "" || remoteUsedAt.After(localUsedAt)):
|
||||
u.lifecycleMode = "remote"
|
||||
u.applyRecentRemoteRecord(remoteRecord)
|
||||
case hasRemote && strings.TrimSpace(remoteRecord.LocalVaultPath) != "" && (localPath == "" || remoteUsedAt.After(localUsedAt)):
|
||||
u.lifecycleMode = "local"
|
||||
u.vaultPath.SetText(strings.TrimSpace(remoteRecord.LocalVaultPath))
|
||||
case localPath != "":
|
||||
u.lifecycleMode = "local"
|
||||
u.vaultPath.SetText(localPath)
|
||||
case hasRemote:
|
||||
u.lifecycleMode = "remote"
|
||||
u.applyRecentRemoteRecord(remoteRecord)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2251,6 +2280,27 @@ func (u *ui) applyAdvancedSyncRemoteCredentialEntry(entry vault.Entry) {
|
||||
u.syncRemotePassword.SetText(entry.Password)
|
||||
}
|
||||
|
||||
func (u *ui) savedAdvancedSyncRemoteBinding() (appstate.ResolvedRemoteBinding, bool) {
|
||||
if !u.hasOpenVault() {
|
||||
return appstate.ResolvedRemoteBinding{}, false
|
||||
}
|
||||
_, resolved, ok, err := u.resolvedSelectedVaultRemoteBinding()
|
||||
if err != nil || !ok {
|
||||
return appstate.ResolvedRemoteBinding{}, false
|
||||
}
|
||||
return resolved, true
|
||||
}
|
||||
|
||||
func (u *ui) prefillAdvancedSyncRemoteFromSavedBinding() {
|
||||
resolved, ok := u.savedAdvancedSyncRemoteBinding()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
u.syncRemoteBaseURL.SetText(resolved.Profile.BaseURL)
|
||||
u.syncRemotePath.SetText(resolved.Profile.Path)
|
||||
u.applyAdvancedSyncRemoteCredentialEntry(resolved.Credentials)
|
||||
}
|
||||
|
||||
func (u *ui) selectVaultRemoteProfile(id string) {
|
||||
id = strings.TrimSpace(id)
|
||||
u.selectedVaultRemoteProfileID = id
|
||||
@@ -2484,6 +2534,18 @@ func (u *ui) directRemoteSyncShortcutLabel() string {
|
||||
return "Use Remote Sync"
|
||||
}
|
||||
|
||||
func (u *ui) shouldShowRemoteSyncSetupShortcut() bool {
|
||||
if !u.hasOpenVault() || u.isVaultLocked() || u.state.Section != appstate.SectionEntries {
|
||||
return false
|
||||
}
|
||||
_, ok := u.selectedVaultRemoteBinding()
|
||||
return !ok
|
||||
}
|
||||
|
||||
func (u *ui) remoteSyncSetupShortcutLabel() string {
|
||||
return "Set Up 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])
|
||||
@@ -4105,6 +4167,9 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
|
||||
}
|
||||
}
|
||||
}
|
||||
for u.useSavedAdvancedSyncRemote.Clicked(gtx) {
|
||||
u.openRemoteSyncSetupDialog()
|
||||
}
|
||||
for u.openSelectedVaultRemote.Clicked(gtx) {
|
||||
if u.lifecycleBusy() {
|
||||
continue
|
||||
@@ -6646,16 +6711,27 @@ func (u *ui) pathBar(gtx layout.Context) layout.Dimensions {
|
||||
return children
|
||||
}()...)
|
||||
}
|
||||
if !u.shouldShowDirectRemoteSyncShortcut() {
|
||||
if !u.shouldShowDirectRemoteSyncShortcut() && !u.shouldShowRemoteSyncSetupShortcut() {
|
||||
return crumbBar(gtx)
|
||||
}
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
children := []layout.FlexChild{
|
||||
layout.Rigid(crumbBar),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
}
|
||||
if u.shouldShowDirectRemoteSyncShortcut() {
|
||||
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.openSelectedVaultRemote, u.directRemoteSyncShortcutLabel())
|
||||
}),
|
||||
)
|
||||
}))
|
||||
}
|
||||
if u.shouldShowRemoteSyncSetupShortcut() {
|
||||
if u.shouldShowDirectRemoteSyncShortcut() {
|
||||
children = append(children, layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout))
|
||||
}
|
||||
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.useSavedAdvancedSyncRemote, u.remoteSyncSetupShortcutLabel())
|
||||
}))
|
||||
}
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, children...)
|
||||
}
|
||||
|
||||
func (u *ui) visibleBreadcrumbs(displayPath []string) ([]string, []int) {
|
||||
|
||||
+143
-7
@@ -4934,14 +4934,11 @@ func TestUIStartupPreselectsNewestTargetAcrossLocalAndRemote(t *testing.T) {
|
||||
|
||||
second := newUIWithSession("desktop", &session.Manager{}, paths)
|
||||
|
||||
if got := second.lifecycleMode; got != "remote" {
|
||||
t.Fatalf("lifecycleMode = %q, want remote", got)
|
||||
if got := second.lifecycleMode; got != "local" {
|
||||
t.Fatalf("lifecycleMode = %q, want local", got)
|
||||
}
|
||||
if got := second.remoteBaseURL.Text(); got != "https://dav.example.com" {
|
||||
t.Fatalf("remoteBaseURL = %q, want https://dav.example.com", got)
|
||||
}
|
||||
if got := second.remotePath.Text(); got != "vaults/home.kdbx" {
|
||||
t.Fatalf("remotePath = %q, want vaults/home.kdbx", got)
|
||||
if got := second.vaultPath.Text(); got != "/tmp/local.kdbx" {
|
||||
t.Fatalf("vaultPath = %q, want /tmp/local.kdbx", got)
|
||||
}
|
||||
if got := second.remoteUsername.Text(); got != "" {
|
||||
t.Fatalf("remoteUsername = %q, want empty for location-only recent remote", got)
|
||||
@@ -5422,6 +5419,31 @@ func TestRestoreStartupLifecycleTargetSelectsMostRecentLocalVault(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestoreStartupLifecycleTargetUsesLocalCacheFromRecentRemote(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
u := newUIWithSession("desktop", &session.Manager{})
|
||||
u.lifecycleMode = "remote"
|
||||
u.vaultPath.SetText("")
|
||||
u.recentVaults = []string{"/tmp/older.kdbx"}
|
||||
u.recentVaultUsedAt["/tmp/older.kdbx"] = time.Date(2026, time.April, 5, 1, 2, 3, 0, time.UTC)
|
||||
u.recentRemotes = []recentRemoteRecord{{
|
||||
BaseURL: "https://dav.example.invalid/remote.php/dav",
|
||||
Path: "files/family/keepass.kdbx",
|
||||
LocalVaultPath: "/tmp/family-cache.kdbx",
|
||||
UsedAt: time.Date(2026, time.April, 5, 2, 2, 3, 0, time.UTC).Format(time.RFC3339Nano),
|
||||
}}
|
||||
|
||||
u.restoreStartupLifecycleTarget()
|
||||
|
||||
if got := u.lifecycleMode; got != "local" {
|
||||
t.Fatalf("lifecycleMode after restore = %q, want local", got)
|
||||
}
|
||||
if got := u.vaultPath.Text(); got != "/tmp/family-cache.kdbx" {
|
||||
t.Fatalf("vaultPath after restore = %q, want /tmp/family-cache.kdbx", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShowLocalVaultChooser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -5795,6 +5817,120 @@ func TestUIDirectRemoteSyncShortcutLabelUsesSyncLanguage(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIShouldShowRemoteSyncSetupShortcutForOpenedLocalVaultWithoutSavedBinding(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
u := newUIWithModel("desktop", vault.Model{
|
||||
Entries: []vault.Entry{{
|
||||
ID: "entry-1",
|
||||
Title: "Vault Console",
|
||||
Path: []string{"Crew", "Internet"},
|
||||
}},
|
||||
})
|
||||
u.state.Section = appstate.SectionEntries
|
||||
|
||||
if !u.shouldShowRemoteSyncSetupShortcut() {
|
||||
t.Fatal("shouldShowRemoteSyncSetupShortcut() = false, want true for opened local vault without saved binding")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIShouldHideRemoteSyncSetupShortcutWhenSavedBindingExists(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.shouldShowRemoteSyncSetupShortcut() {
|
||||
t.Fatal("shouldShowRemoteSyncSetupShortcut() = true, want false when saved binding already exists")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIRemoteSyncSetupShortcutLabelUsesClearLanguage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
u := newUIWithSession("desktop", &session.Manager{})
|
||||
|
||||
if got := u.remoteSyncSetupShortcutLabel(); got != "Set Up Remote Sync" {
|
||||
t.Fatalf("remoteSyncSetupShortcutLabel() = %q, want Set Up Remote Sync", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIOpenRemoteSyncSetupDialogPrefillsCurrentVaultSetupFlow(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
u := newUIWithModel("desktop", vault.Model{
|
||||
Entries: []vault.Entry{{
|
||||
ID: "remote-creds-1",
|
||||
Title: "WebDAV Sign-In",
|
||||
Username: "tjulian",
|
||||
Password: "token-1",
|
||||
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.vaultPath.SetText("/vaults/family.kdbx")
|
||||
|
||||
u.openRemoteSyncSetupDialog()
|
||||
|
||||
if !u.syncDialogOpen {
|
||||
t.Fatal("syncDialogOpen = false, want true")
|
||||
}
|
||||
if got := u.syncDialogPurpose; got != syncDialogPurposeRemoteSetup {
|
||||
t.Fatalf("syncDialogPurpose = %q, want remote setup", got)
|
||||
}
|
||||
if got := u.syncSourceMode; got != syncSourceRemote {
|
||||
t.Fatalf("syncSourceMode = %q, want remote", got)
|
||||
}
|
||||
if got := u.syncDirection; got != syncDirectionPush {
|
||||
t.Fatalf("syncDirection = %q, want push", got)
|
||||
}
|
||||
if got := u.syncLocalPath.Text(); got != "/vaults/family.kdbx" {
|
||||
t.Fatalf("syncLocalPath = %q, want current vault path", got)
|
||||
}
|
||||
if got := u.syncRemoteBaseURL.Text(); got != "https://dav.example.invalid/remote.php/dav" {
|
||||
t.Fatalf("syncRemoteBaseURL = %q, want saved remote base URL", got)
|
||||
}
|
||||
if got := u.syncRemotePath.Text(); got != "files/family/keepass.kdbx" {
|
||||
t.Fatalf("syncRemotePath = %q, want saved remote path", got)
|
||||
}
|
||||
if got := u.syncRemoteUsername.Text(); got != "tjulian" {
|
||||
t.Fatalf("syncRemoteUsername = %q, want tjulian", got)
|
||||
}
|
||||
if got := u.syncRemotePassword.Text(); got != "token-1" {
|
||||
t.Fatalf("syncRemotePassword = %q, want token-1", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUISelectedLocalVaultRemoteSyncSummaryMentionsSetup(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
u := newUIWithSession("desktop", &session.Manager{})
|
||||
|
||||
if got := u.selectedLocalVaultRemoteSyncSummary("/vaults/family.kdbx"); got != "Open this vault to set up a WebDAV sync target for it." {
|
||||
t.Fatalf("selectedLocalVaultRemoteSyncSummary() = %q, want setup guidance", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUISaveCurrentRemoteBindingActionPersistsBindingIntoVault(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
+13
-184
@@ -21,7 +21,6 @@ import (
|
||||
func (u *ui) lifecycleControls(gtx layout.Context) layout.Dimensions {
|
||||
busy := u.lifecycleBusy()
|
||||
showLocalChooser := u.showLocalVaultChooser()
|
||||
showRemoteChooser := u.showRemoteConnectionChooser()
|
||||
selectedLocalPath := strings.TrimSpace(u.vaultPath.Text())
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
@@ -31,172 +30,13 @@ func (u *ui) lifecycleControls(gtx layout.Context) layout.Dimensions {
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
message := "Choose a recent vault or enter a .kdbx path, then unlock it."
|
||||
if u.lifecycleMode == "remote" {
|
||||
message = u.remoteLifecycleMessage()
|
||||
}
|
||||
message := "Choose a recent vault or enter a .kdbx path, then unlock it. Remote sync attaches to that local vault after it opens."
|
||||
lbl := material.Label(u.theme, unit.Sp(14), message)
|
||||
lbl.Color = accentColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if busy {
|
||||
return passiveSectionTab(gtx, u.theme, "Local Vault", u.lifecycleMode == "local")
|
||||
}
|
||||
return sectionTabButton(gtx, u.theme, &u.showLocalLifecycle, "Local Vault", u.lifecycleMode == "local")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if busy {
|
||||
return passiveSectionTab(gtx, u.theme, "Remote Vault", u.lifecycleMode == "remote")
|
||||
}
|
||||
return sectionTabButton(gtx, u.theme, &u.showRemoteLifecycle, "Remote Vault", u.lifecycleMode == "remote")
|
||||
}),
|
||||
)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if u.lifecycleMode == "remote" {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !showRemoteChooser {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
lbl := material.Label(u.theme, unit.Sp(12), "LOCATION")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !showRemoteChooser {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return layout.Spacer{Height: unit.Dp(4)}.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !showRemoteChooser {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return labeledEditorHelp(u.theme, "Remote Base URL", "Base WebDAV endpoint, for example https://server/remote.php/webdav.", &u.remoteBaseURL, false)(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !showRemoteChooser {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return layout.Spacer{Height: unit.Dp(6)}.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !showRemoteChooser {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return labeledEditorHelp(u.theme, "Remote Path", "Path to the remote .kdbx file under the WebDAV base URL.", &u.remotePath, false)(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !showRemoteChooser {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return layout.Spacer{Height: unit.Dp(6)}.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if showRemoteChooser || !u.hasSelectedRemoteTarget() {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return layout.Dimensions{}
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if showRemoteChooser && !busy {
|
||||
return u.recentRemoteList(gtx)
|
||||
}
|
||||
return layout.Dimensions{}
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !showRemoteChooser {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return layout.Spacer{Height: unit.Dp(10)}.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !showRemoteChooser {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
lbl := material.Label(u.theme, unit.Sp(12), "AUTHENTICATION")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !showRemoteChooser {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return layout.Spacer{Height: unit.Dp(4)}.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !showRemoteChooser {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return labeledEditorHelp(u.theme, "Remote Username", "Username used to authenticate to the WebDAV server.", &u.remoteUsername, false)(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !showRemoteChooser {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return layout.Spacer{Height: unit.Dp(6)}.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !showRemoteChooser {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return labeledEditorHelp(u.theme, "Remote Password", "Password or app token used to authenticate to the WebDAV server.", &u.remotePassword, true)(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !showRemoteChooser {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return layout.Spacer{Height: unit.Dp(6)}.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !showRemoteChooser {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return layout.Inset{Top: unit.Dp(4)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.openRemotePrefsHelp, "Settings & Help")
|
||||
})
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !showRemoteChooser {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return layout.Spacer{Height: unit.Dp(8)}.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !showRemoteChooser {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(13), "Local Cache Bootstrap")
|
||||
lbl.Color = accentColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(12), u.remoteLifecycleSetupSummary())
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !showRemoteChooser {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return layout.Spacer{Height: unit.Dp(10)}.Layout(gtx)
|
||||
}),
|
||||
)
|
||||
}
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !showLocalChooser {
|
||||
@@ -326,29 +166,6 @@ func (u *ui) lifecycleControls(gtx layout.Context) layout.Dimensions {
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if u.lifecycleMode == "remote" {
|
||||
label := u.remoteOpenButtonLabel()
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if busy {
|
||||
return passiveTonedButton(gtx, u.theme, label)
|
||||
}
|
||||
return tonedButton(gtx, u.theme, &u.openRemote, label)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if busy || !u.hasSelectedRemoteTarget() {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return layout.Spacer{Height: unit.Dp(8)}.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if busy || !u.hasSelectedRemoteTarget() {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return u.selectedRemoteConnectionCard(gtx)
|
||||
}),
|
||||
)
|
||||
}
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
label := "Open Vault"
|
||||
@@ -499,6 +316,11 @@ func (u *ui) selectedLocalVaultCard(gtx layout.Context, path string) layout.Dime
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(11), u.selectedLocalVaultRemoteSyncSummary(path))
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.clearVaultSelection, "Open Different Vault")
|
||||
@@ -508,6 +330,13 @@ func (u *ui) selectedLocalVaultCard(gtx layout.Context, path string) layout.Dime
|
||||
})
|
||||
}
|
||||
|
||||
func (u *ui) selectedLocalVaultRemoteSyncSummary(path string) string {
|
||||
if record, ok := u.boundRecentRemoteForLocalVault(path); ok {
|
||||
return "Saved remote sync target: " + friendlyRecentRemoteLabel(record)
|
||||
}
|
||||
return "Open this vault to set up a WebDAV sync target for it."
|
||||
}
|
||||
|
||||
func (u *ui) lifecycleSecuritySettingsSummary() string {
|
||||
return "Cipher and KDF now live in Vault Settings so opening and creating a vault stays focused on the file, key material, and sync choices."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user