Tighten banner and navigation behavior
This commit is contained in:
+1
-1
@@ -96,7 +96,7 @@ func (s *State) SetSearchQuery(query string) {
|
||||
|
||||
func (s *State) BeginNewEntry() {
|
||||
s.SelectedEntryID = ""
|
||||
s.StatusMessage = "new entry form ready"
|
||||
s.StatusMessage = ""
|
||||
s.ErrorMessage = ""
|
||||
}
|
||||
|
||||
|
||||
@@ -922,7 +922,7 @@ func TestSetSearchQueryUpdatesControllerSearchState(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBeginNewEntryClearsSelectionAndSetsStatus(t *testing.T) {
|
||||
func TestBeginNewEntryClearsSelectionAndStatus(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
state := State{
|
||||
@@ -935,8 +935,8 @@ func TestBeginNewEntryClearsSelectionAndSetsStatus(t *testing.T) {
|
||||
if state.SelectedEntryID != "" {
|
||||
t.Fatalf("SelectedEntryID = %q, want empty", state.SelectedEntryID)
|
||||
}
|
||||
if state.StatusMessage != "new entry form ready" {
|
||||
t.Fatalf("StatusMessage = %q, want new entry form ready", state.StatusMessage)
|
||||
if state.StatusMessage != "" {
|
||||
t.Fatalf("StatusMessage = %q, want empty", state.StatusMessage)
|
||||
}
|
||||
if state.ErrorMessage != "" {
|
||||
t.Fatalf("ErrorMessage = %q, want empty", state.ErrorMessage)
|
||||
|
||||
@@ -41,7 +41,7 @@ const (
|
||||
|
||||
const (
|
||||
maxAttachmentBytes = 10 << 20
|
||||
statusBannerDuration = 4 * time.Second
|
||||
statusBannerDuration = 2600 * time.Millisecond
|
||||
)
|
||||
|
||||
type bannerKind string
|
||||
@@ -79,6 +79,7 @@ type statePaths struct {
|
||||
DefaultSaveAsPath string
|
||||
RecentVaultsPath string
|
||||
RecentRemotesPath string
|
||||
UIPreferencesPath string
|
||||
}
|
||||
|
||||
type recentVaultRecord struct {
|
||||
@@ -94,6 +95,10 @@ type recentRemoteRecord struct {
|
||||
LastGroup []string `json:"lastGroup,omitempty"`
|
||||
}
|
||||
|
||||
type uiPreferences struct {
|
||||
GroupControlsHidden bool `json:"groupControlsHidden"`
|
||||
}
|
||||
|
||||
type ui struct {
|
||||
mode string
|
||||
theme *material.Theme
|
||||
@@ -201,6 +206,7 @@ type ui struct {
|
||||
keyboardFocus focusID
|
||||
defaultSaveAsPath string
|
||||
recentVaultsPath string
|
||||
uiPreferencesPath string
|
||||
recentRemotesPath string
|
||||
editingEntry bool
|
||||
groupControlsHidden bool
|
||||
@@ -293,6 +299,7 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths)
|
||||
lifecycleMode: "local",
|
||||
defaultSaveAsPath: paths.DefaultSaveAsPath,
|
||||
recentVaultsPath: paths.RecentVaultsPath,
|
||||
uiPreferencesPath: paths.UIPreferencesPath,
|
||||
recentRemotesPath: paths.RecentRemotesPath,
|
||||
recentVaultGroups: map[string][]string{},
|
||||
now: time.Now,
|
||||
@@ -309,6 +316,7 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths)
|
||||
u.setCustomFieldRows(nil)
|
||||
u.loadRecentVaults()
|
||||
u.loadRecentRemotes()
|
||||
u.loadUIPreferences()
|
||||
u.filter()
|
||||
return u
|
||||
}
|
||||
@@ -342,6 +350,7 @@ func defaultStatePaths(stateDir string) statePaths {
|
||||
DefaultSaveAsPath: filepath.Join(baseDir, "vault.kdbx"),
|
||||
RecentVaultsPath: filepath.Join(baseDir, "recent-vaults.json"),
|
||||
RecentRemotesPath: filepath.Join(baseDir, "recent-remotes.json"),
|
||||
UIPreferencesPath: filepath.Join(baseDir, "ui-prefs.json"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -805,6 +814,37 @@ func (u *ui) saveRecentRemotes() {
|
||||
_ = os.WriteFile(u.recentRemotesPath, content, 0o600)
|
||||
}
|
||||
|
||||
func (u *ui) loadUIPreferences() {
|
||||
if strings.TrimSpace(u.uiPreferencesPath) == "" {
|
||||
return
|
||||
}
|
||||
content, err := os.ReadFile(u.uiPreferencesPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var prefs uiPreferences
|
||||
if err := json.Unmarshal(content, &prefs); err != nil {
|
||||
return
|
||||
}
|
||||
u.groupControlsHidden = prefs.GroupControlsHidden
|
||||
}
|
||||
|
||||
func (u *ui) saveUIPreferences() {
|
||||
if strings.TrimSpace(u.uiPreferencesPath) == "" {
|
||||
return
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(u.uiPreferencesPath), 0o700); err != nil {
|
||||
return
|
||||
}
|
||||
content, err := json.MarshalIndent(uiPreferences{
|
||||
GroupControlsHidden: u.groupControlsHidden,
|
||||
}, "", " ")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = os.WriteFile(u.uiPreferencesPath, content, 0o600)
|
||||
}
|
||||
|
||||
func (u *ui) noteRecentRemote(baseURL, path, username, password string, rememberAuth bool) {
|
||||
baseURL = strings.TrimSpace(baseURL)
|
||||
path = strings.TrimSpace(path)
|
||||
@@ -1398,6 +1438,7 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
|
||||
}
|
||||
for u.toggleGroupControls.Clicked(gtx) {
|
||||
u.groupControlsHidden = !u.groupControlsHidden
|
||||
u.saveUIPreferences()
|
||||
}
|
||||
for u.renameGroup.Clicked(gtx) {
|
||||
u.clearDeleteGroupConfirmation()
|
||||
@@ -1644,11 +1685,21 @@ func (u *ui) navigationHeader(gtx layout.Context) layout.Dimensions {
|
||||
func (u *ui) sectionBar(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.showEntries, "Entries")
|
||||
btn := material.Button(u.theme, &u.showEntries, "Entries")
|
||||
btn.Background = accentColor
|
||||
btn.Color = color.NRGBA{R: 255, G: 252, B: 247, A: 255}
|
||||
btn.TextSize = unit.Sp(11)
|
||||
btn.Inset = layout.Inset{Top: 5, Bottom: 5, Left: 9, Right: 9}
|
||||
return btn.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.showRecycle, "Recycle Bin")
|
||||
btn := material.Button(u.theme, &u.showRecycle, "Recycle Bin")
|
||||
btn.Background = accentColor
|
||||
btn.Color = color.NRGBA{R: 255, G: 252, B: 247, A: 255}
|
||||
btn.TextSize = unit.Sp(11)
|
||||
btn.Inset = layout.Inset{Top: 5, Bottom: 5, Left: 9, Right: 9}
|
||||
return btn.Layout(gtx)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1737,6 +1737,9 @@ func TestUIStatusBannerExpiresAfterTimeout(t *testing.T) {
|
||||
u := newUIWithModel("desktop", vault.Model{})
|
||||
u.now = func() time.Time { return now }
|
||||
u.state.StatusMessage = "synchronize vault complete"
|
||||
if statusBannerDuration != 2600*time.Millisecond {
|
||||
t.Fatalf("statusBannerDuration = %v, want 2.6s", statusBannerDuration)
|
||||
}
|
||||
u.statusExpiresAt = now.Add(statusBannerDuration)
|
||||
|
||||
if got := u.bannerSurface(); got.Kind != bannerStatus || got.Message != "synchronize vault complete" {
|
||||
@@ -2003,6 +2006,26 @@ func TestUIRecentRemoteConnectionsPersistAndReload(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIGroupToolsDisclosureStatePersists(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
configPath := filepath.Join(t.TempDir(), "ui-prefs.json")
|
||||
|
||||
first := newUIWithSession("desktop", &session.Manager{})
|
||||
first.uiPreferencesPath = configPath
|
||||
first.groupControlsHidden = true
|
||||
first.saveUIPreferences()
|
||||
|
||||
second := newUIWithSession("desktop", &session.Manager{})
|
||||
second.uiPreferencesPath = configPath
|
||||
second.groupControlsHidden = false
|
||||
second.loadUIPreferences()
|
||||
|
||||
if !second.groupControlsHidden {
|
||||
t.Fatal("groupControlsHidden = false after reload, want true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectingRecentRemoteConnectionKeepsPasswordMasked(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -2107,6 +2130,9 @@ func TestDefaultStatePathsUsesProvidedStateDir(t *testing.T) {
|
||||
if got := paths.RecentRemotesPath; got != filepath.Join(base, "recent-remotes.json") {
|
||||
t.Fatalf("RecentRemotesPath = %q, want %q", got, filepath.Join(base, "recent-remotes.json"))
|
||||
}
|
||||
if got := paths.UIPreferencesPath; got != filepath.Join(base, "ui-prefs.json") {
|
||||
t.Fatalf("UIPreferencesPath = %q, want %q", got, filepath.Join(base, "ui-prefs.json"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultStatePathsUsesEnvironmentStateDirWhenFlagUnset(t *testing.T) {
|
||||
@@ -2124,6 +2150,9 @@ func TestDefaultStatePathsUsesEnvironmentStateDirWhenFlagUnset(t *testing.T) {
|
||||
if got := paths.RecentRemotesPath; got != filepath.Join(base, "recent-remotes.json") {
|
||||
t.Fatalf("RecentRemotesPath = %q, want %q", got, filepath.Join(base, "recent-remotes.json"))
|
||||
}
|
||||
if got := paths.UIPreferencesPath; got != filepath.Join(base, "ui-prefs.json") {
|
||||
t.Fatalf("UIPreferencesPath = %q, want %q", got, filepath.Join(base, "ui-prefs.json"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveFlagOrEnvPrefersFlagThenEnvThenFallback(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user