Improve KeePassGO vault navigation UX
This commit is contained in:
@@ -143,6 +143,7 @@ type ui struct {
|
|||||||
attachmentClicks []widget.Clickable
|
attachmentClicks []widget.Clickable
|
||||||
breadcrumbs []widget.Clickable
|
breadcrumbs []widget.Clickable
|
||||||
groupClicks []widget.Clickable
|
groupClicks []widget.Clickable
|
||||||
|
recentVaultClicks []widget.Clickable
|
||||||
state appstate.State
|
state appstate.State
|
||||||
visible []entry
|
visible []entry
|
||||||
currentPath []string
|
currentPath []string
|
||||||
@@ -164,6 +165,7 @@ type ui struct {
|
|||||||
keyboardFocus focusID
|
keyboardFocus focusID
|
||||||
defaultSaveAsPath string
|
defaultSaveAsPath string
|
||||||
editingEntry bool
|
editingEntry bool
|
||||||
|
recentVaults []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -425,6 +427,7 @@ func (u *ui) createVaultAction() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
u.vaultPath.SetText(u.saveAsTargetPath())
|
u.vaultPath.SetText(u.saveAsTargetPath())
|
||||||
|
u.noteRecentVault(u.saveAsTargetPath())
|
||||||
}
|
}
|
||||||
u.currentPath = append([]string(nil), u.state.CurrentPath...)
|
u.currentPath = append([]string(nil), u.state.CurrentPath...)
|
||||||
u.editingEntry = false
|
u.editingEntry = false
|
||||||
@@ -444,7 +447,9 @@ func (u *ui) openVaultAction() error {
|
|||||||
if err := u.state.OpenVault(path, key); err != nil {
|
if err := u.state.OpenVault(path, key); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
u.noteRecentVault(path)
|
||||||
u.currentPath = append([]string(nil), u.state.CurrentPath...)
|
u.currentPath = append([]string(nil), u.state.CurrentPath...)
|
||||||
|
u.enterHiddenVaultRoot()
|
||||||
u.editingEntry = false
|
u.editingEntry = false
|
||||||
u.filter()
|
u.filter()
|
||||||
return nil
|
return nil
|
||||||
@@ -464,6 +469,7 @@ func (u *ui) saveAsAction() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
u.vaultPath.SetText(path)
|
u.vaultPath.SetText(path)
|
||||||
|
u.noteRecentVault(path)
|
||||||
u.filter()
|
u.filter()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -481,6 +487,7 @@ func (u *ui) openRemoteAction() error {
|
|||||||
if err := u.state.OpenRemoteVault(client, strings.TrimSpace(u.remotePath.Text()), key); err != nil {
|
if err := u.state.OpenRemoteVault(client, strings.TrimSpace(u.remotePath.Text()), key); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
u.enterHiddenVaultRoot()
|
||||||
u.editingEntry = false
|
u.editingEntry = false
|
||||||
u.filter()
|
u.filter()
|
||||||
return nil
|
return nil
|
||||||
@@ -535,6 +542,70 @@ func (u *ui) saveAsTargetPath() string {
|
|||||||
return u.defaultSaveAsPath
|
return u.defaultSaveAsPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ui) noteRecentVault(path string) {
|
||||||
|
path = strings.TrimSpace(path)
|
||||||
|
if path == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next := []string{path}
|
||||||
|
for _, existing := range u.recentVaults {
|
||||||
|
if existing == path {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
next = append(next, existing)
|
||||||
|
if len(next) == 6 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u.recentVaults = next
|
||||||
|
if len(u.recentVaultClicks) < len(u.recentVaults) {
|
||||||
|
u.recentVaultClicks = make([]widget.Clickable, len(u.recentVaults))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) hiddenVaultRoot() string {
|
||||||
|
if u.state.Section != appstate.SectionEntries {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
model, err := u.state.Session.Current()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if len(model.EntriesInPath(nil)) != 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
groups := model.ChildGroups(nil)
|
||||||
|
if len(groups) != 1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return groups[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) enterHiddenVaultRoot() {
|
||||||
|
root := u.hiddenVaultRoot()
|
||||||
|
if root == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u.setCurrentPath([]string{root})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) displayPath() []string {
|
||||||
|
path := append([]string(nil), u.currentPath...)
|
||||||
|
root := u.hiddenVaultRoot()
|
||||||
|
if root == "" || len(path) == 0 || path[0] != root {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return append([]string(nil), path[1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) displayEntryPath(path []string) []string {
|
||||||
|
root := u.hiddenVaultRoot()
|
||||||
|
if root == "" || len(path) == 0 || path[0] != root {
|
||||||
|
return append([]string(nil), path...)
|
||||||
|
}
|
||||||
|
return append([]string(nil), path[1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ui) runAction(label string, action func() error) {
|
func (u *ui) runAction(label string, action func() error) {
|
||||||
u.loadingMessage = actionLoadingLabel(label)
|
u.loadingMessage = actionLoadingLabel(label)
|
||||||
if err := action(); err != nil {
|
if err := action(); err != nil {
|
||||||
@@ -756,10 +827,17 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
|
|||||||
for u.pickKeyFile.Clicked(gtx) {
|
for u.pickKeyFile.Clicked(gtx) {
|
||||||
u.runAction("choose key file", func() error { return u.chooseExistingFileAction(&u.keyFilePath) })
|
u.runAction("choose key file", func() error { return u.chooseExistingFileAction(&u.keyFilePath) })
|
||||||
}
|
}
|
||||||
|
for i := range u.recentVaultClicks {
|
||||||
|
for u.recentVaultClicks[i].Clicked(gtx) {
|
||||||
|
if i < len(u.recentVaults) {
|
||||||
|
u.vaultPath.SetText(u.recentVaults[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for u.addEntry.Clicked(gtx) {
|
for u.addEntry.Clicked(gtx) {
|
||||||
u.state.BeginNewEntry()
|
u.state.BeginNewEntry()
|
||||||
u.loadSelectedEntryIntoEditor()
|
u.loadSelectedEntryIntoEditor()
|
||||||
u.entryPath.SetText(strings.Join(u.state.CurrentPath, " / "))
|
u.entryPath.SetText(strings.Join(u.displayPath(), " / "))
|
||||||
u.editingEntry = true
|
u.editingEntry = true
|
||||||
}
|
}
|
||||||
for u.saveEntry.Clicked(gtx) {
|
for u.saveEntry.Clicked(gtx) {
|
||||||
@@ -903,7 +981,6 @@ func (u *ui) header(gtx layout.Context) layout.Dimensions {
|
|||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(u.feedbackBanner),
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -929,7 +1006,6 @@ func (u *ui) header(gtx layout.Context) layout.Dimensions {
|
|||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.Layout),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(u.feedbackBanner),
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1250,7 +1326,7 @@ func (u *ui) detailPanel(gtx layout.Context) layout.Dimensions {
|
|||||||
return lbl.Layout(gtx)
|
return lbl.Layout(gtx)
|
||||||
},
|
},
|
||||||
layout.Spacer{Height: titlePad}.Layout,
|
layout.Spacer{Height: titlePad}.Layout,
|
||||||
detailLine(u.theme, "Path", strings.Join(item.Path, " / ")),
|
detailLine(u.theme, "Path", strings.Join(u.displayEntryPath(item.Path), " / ")),
|
||||||
layout.Spacer{Height: sectionGap}.Layout,
|
layout.Spacer{Height: sectionGap}.Layout,
|
||||||
detailLine(u.theme, "Username", item.Username),
|
detailLine(u.theme, "Username", item.Username),
|
||||||
layout.Spacer{Height: sectionGap}.Layout,
|
layout.Spacer{Height: sectionGap}.Layout,
|
||||||
@@ -1489,7 +1565,8 @@ func (u *ui) pathBar(gtx layout.Context) layout.Dimensions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
u.syncCurrentPath()
|
u.syncCurrentPath()
|
||||||
crumbs := append([]string{"All Entries"}, append([]string{}, u.currentPath...)...)
|
displayPath := u.displayPath()
|
||||||
|
crumbs := append([]string{"/"}, append([]string{}, displayPath...)...)
|
||||||
if u.state.Section == appstate.SectionTemplates {
|
if u.state.Section == appstate.SectionTemplates {
|
||||||
crumbs = append([]string{"Templates"}, append([]string{}, u.currentPath...)...)
|
crumbs = append([]string{"Templates"}, append([]string{}, u.currentPath...)...)
|
||||||
}
|
}
|
||||||
@@ -1501,9 +1578,19 @@ func (u *ui) pathBar(gtx layout.Context) layout.Dimensions {
|
|||||||
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
for u.breadcrumbs[index].Clicked(gtx) {
|
for u.breadcrumbs[index].Clicked(gtx) {
|
||||||
if index == 0 {
|
if index == 0 {
|
||||||
u.setCurrentPath(nil)
|
root := u.hiddenVaultRoot()
|
||||||
|
if root == "" {
|
||||||
|
u.setCurrentPath(nil)
|
||||||
|
} else {
|
||||||
|
u.setCurrentPath([]string{root})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
u.setCurrentPath(crumbs[1 : index+1])
|
nextPath := crumbs[1 : index+1]
|
||||||
|
root := u.hiddenVaultRoot()
|
||||||
|
if root != "" {
|
||||||
|
nextPath = append([]string{root}, nextPath...)
|
||||||
|
}
|
||||||
|
u.setCurrentPath(nextPath)
|
||||||
}
|
}
|
||||||
u.filter()
|
u.filter()
|
||||||
}
|
}
|
||||||
@@ -1533,8 +1620,8 @@ func (u *ui) groupBar(gtx layout.Context) layout.Dimensions {
|
|||||||
if len(groups) == 0 {
|
if len(groups) == 0 {
|
||||||
return layout.Dimensions{}
|
return layout.Dimensions{}
|
||||||
}
|
}
|
||||||
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx, func() []layout.FlexChild {
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, func() []layout.FlexChild {
|
||||||
children := make([]layout.FlexChild, 0, len(groups))
|
children := make([]layout.FlexChild, 0, len(groups)*2)
|
||||||
for i, group := range groups {
|
for i, group := range groups {
|
||||||
idx := i
|
idx := i
|
||||||
name := group
|
name := group
|
||||||
@@ -1550,6 +1637,9 @@ func (u *ui) groupBar(gtx layout.Context) layout.Dimensions {
|
|||||||
btn.TextSize = unit.Sp(12)
|
btn.TextSize = unit.Sp(12)
|
||||||
return btn.Layout(gtx)
|
return btn.Layout(gtx)
|
||||||
}))
|
}))
|
||||||
|
if i < len(groups)-1 {
|
||||||
|
children = append(children, layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return children
|
return children
|
||||||
}()...)
|
}()...)
|
||||||
@@ -1571,27 +1661,6 @@ func detailLine(th *material.Theme, label, value string) layout.Widget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) feedbackBanner(gtx layout.Context) layout.Dimensions {
|
|
||||||
message := u.state.StatusMessage
|
|
||||||
tone := color.NRGBA{R: 231, G: 239, B: 235, A: 255}
|
|
||||||
textColor := accentColor
|
|
||||||
if u.state.ErrorMessage != "" {
|
|
||||||
message = u.state.ErrorMessage
|
|
||||||
tone = color.NRGBA{R: 248, G: 226, B: 223, A: 255}
|
|
||||||
textColor = color.NRGBA{R: 140, G: 46, B: 34, A: 255}
|
|
||||||
}
|
|
||||||
if message == "" {
|
|
||||||
return layout.Dimensions{}
|
|
||||||
}
|
|
||||||
return layout.Background{}.Layout(gtx, fill(tone), func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return layout.UniformInset(unit.Dp(10)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
lbl := material.Label(u.theme, unit.Sp(13), message)
|
|
||||||
lbl.Color = textColor
|
|
||||||
return lbl.Layout(gtx)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ui) passwordLine(label, value string) layout.Widget {
|
func (u *ui) passwordLine(label, value string) layout.Widget {
|
||||||
return func(gtx layout.Context) layout.Dimensions {
|
return func(gtx layout.Context) layout.Dimensions {
|
||||||
icon := u.eyeIcon
|
icon := u.eyeIcon
|
||||||
|
|||||||
@@ -1694,6 +1694,53 @@ func TestUIRequiresExplicitEditModeForEntryEditor(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUIAutoEntersSingleVaultRootGroupAndDisplaysSlashRoot(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
path := filepath.Join(t.TempDir(), "keepass.kdbx")
|
||||||
|
var encoded bytes.Buffer
|
||||||
|
if err := vault.SaveKDBX(&encoded, vault.Model{
|
||||||
|
Entries: []vault.Entry{
|
||||||
|
{ID: "vault-console", Title: "Vault Console", Path: []string{"keepass", "Crew", "Internet"}},
|
||||||
|
},
|
||||||
|
}, "correct horse battery staple"); err != nil {
|
||||||
|
t.Fatalf("SaveKDBX() error = %v", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(path, encoded.Bytes(), 0o600); err != nil {
|
||||||
|
t.Fatalf("WriteFile(keepass.kdbx) error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
u := newUIWithSession("desktop", &session.Manager{})
|
||||||
|
u.masterPassword.SetText("correct horse battery staple")
|
||||||
|
u.vaultPath.SetText(path)
|
||||||
|
if err := u.openVaultAction(); err != nil {
|
||||||
|
t.Fatalf("openVaultAction() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := u.currentPath; !slices.Equal(got, []string{"keepass"}) {
|
||||||
|
t.Fatalf("currentPath = %v, want [keepass]", got)
|
||||||
|
}
|
||||||
|
if got := u.displayPath(); len(got) != 0 {
|
||||||
|
t.Fatalf("displayPath() = %v, want root slash path", got)
|
||||||
|
}
|
||||||
|
if got := u.childGroups(); !slices.Equal(got, []string{"Crew"}) {
|
||||||
|
t.Fatalf("childGroups() = %v, want [Crew]", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUINoteRecentVaultDeduplicatesAndOrdersMostRecentFirst(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
u := newUIWithSession("desktop", &session.Manager{})
|
||||||
|
u.noteRecentVault("/tmp/one.kdbx")
|
||||||
|
u.noteRecentVault("/tmp/two.kdbx")
|
||||||
|
u.noteRecentVault("/tmp/one.kdbx")
|
||||||
|
|
||||||
|
if got := u.recentVaults; !slices.Equal(got, []string{"/tmp/one.kdbx", "/tmp/two.kdbx"}) {
|
||||||
|
t.Fatalf("recentVaults = %v, want [/tmp/one.kdbx /tmp/two.kdbx]", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUICopyActionsWriteExpectedClipboardContentsAndSanitizedFeedback(t *testing.T) {
|
func TestUICopyActionsWriteExpectedClipboardContentsAndSanitizedFeedback(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
+5
-2
@@ -50,7 +50,7 @@ func (u *ui) loadSelectedEntryIntoEditor() {
|
|||||||
u.entryURL.SetText("")
|
u.entryURL.SetText("")
|
||||||
u.entryNotes.SetText("")
|
u.entryNotes.SetText("")
|
||||||
u.entryTags.SetText("")
|
u.entryTags.SetText("")
|
||||||
u.entryPath.SetText(strings.Join(u.state.CurrentPath, " / "))
|
u.entryPath.SetText(strings.Join(u.displayPath(), " / "))
|
||||||
u.entryFields.SetText("")
|
u.entryFields.SetText("")
|
||||||
u.attachmentName.SetText("")
|
u.attachmentName.SetText("")
|
||||||
u.attachmentPath.SetText("")
|
u.attachmentPath.SetText("")
|
||||||
@@ -65,7 +65,7 @@ func (u *ui) loadSelectedEntryIntoEditor() {
|
|||||||
u.entryURL.SetText(item.URL)
|
u.entryURL.SetText(item.URL)
|
||||||
u.entryNotes.SetText(item.Notes)
|
u.entryNotes.SetText(item.Notes)
|
||||||
u.entryTags.SetText(strings.Join(item.Tags, ", "))
|
u.entryTags.SetText(strings.Join(item.Tags, ", "))
|
||||||
u.entryPath.SetText(strings.Join(item.Path, " / "))
|
u.entryPath.SetText(strings.Join(u.displayEntryPath(item.Path), " / "))
|
||||||
u.entryFields.SetText(marshalFields(item.Fields))
|
u.entryFields.SetText(marshalFields(item.Fields))
|
||||||
u.attachmentName.SetText("")
|
u.attachmentName.SetText("")
|
||||||
u.attachmentPath.SetText("")
|
u.attachmentPath.SetText("")
|
||||||
@@ -330,6 +330,9 @@ func (u *ui) generatePasswordAction() error {
|
|||||||
|
|
||||||
func (u *ui) editorEntry() (vault.Entry, error) {
|
func (u *ui) editorEntry() (vault.Entry, error) {
|
||||||
path := parsePath(u.entryPath.Text())
|
path := parsePath(u.entryPath.Text())
|
||||||
|
if root := u.hiddenVaultRoot(); root != "" && (len(path) == 0 || path[0] != root) {
|
||||||
|
path = append([]string{root}, path...)
|
||||||
|
}
|
||||||
fields, err := parseFields(u.entryFields.Text())
|
fields, err := parseFields(u.entryFields.Text())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return vault.Entry{}, err
|
return vault.Entry{}, err
|
||||||
|
|||||||
+36
-1
@@ -43,6 +43,8 @@ func (u *ui) lifecycleControls(gtx layout.Context) layout.Dimensions {
|
|||||||
}
|
}
|
||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
layout.Rigid(selectorEditorHelp(u.theme, "Vault Path", "Choose the existing .kdbx file to open.", &u.vaultPath, &u.pickVaultPath, "Choose File", false)),
|
layout.Rigid(selectorEditorHelp(u.theme, "Vault Path", "Choose the existing .kdbx file to open.", &u.vaultPath, &u.pickVaultPath, "Choose File", false)),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||||
|
layout.Rigid(u.recentVaultList),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||||
@@ -63,6 +65,39 @@ func (u *ui) lifecycleControls(gtx layout.Context) layout.Dimensions {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ui) recentVaultList(gtx layout.Context) layout.Dimensions {
|
||||||
|
if len(u.recentVaults) == 0 {
|
||||||
|
return layout.Dimensions{}
|
||||||
|
}
|
||||||
|
if len(u.recentVaultClicks) < len(u.recentVaults) {
|
||||||
|
u.recentVaultClicks = make([]widget.Clickable, len(u.recentVaults))
|
||||||
|
}
|
||||||
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), "RECENTLY OPENED")
|
||||||
|
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 layout.Flex{Axis: layout.Vertical}.Layout(gtx, func() []layout.FlexChild {
|
||||||
|
children := make([]layout.FlexChild, 0, len(u.recentVaults)*2)
|
||||||
|
for i, path := range u.recentVaults {
|
||||||
|
index := i
|
||||||
|
label := path
|
||||||
|
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.recentVaultClicks[index], label)
|
||||||
|
}))
|
||||||
|
if i < len(u.recentVaults)-1 {
|
||||||
|
children = append(children, layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
}()...)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ui) attachmentList(gtx layout.Context) layout.Dimensions {
|
func (u *ui) attachmentList(gtx layout.Context) layout.Dimensions {
|
||||||
items := u.selectedAttachmentItems()
|
items := u.selectedAttachmentItems()
|
||||||
if len(items) == 0 {
|
if len(items) == 0 {
|
||||||
@@ -104,7 +139,7 @@ func (u *ui) groupControls(gtx layout.Context) layout.Dimensions {
|
|||||||
return tonedButton(gtx, u.theme, &u.createGroup, "Create Group")
|
return tonedButton(gtx, u.theme, &u.createGroup, "Create Group")
|
||||||
}),
|
}),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
if len(u.currentPath) == 0 {
|
if len(u.displayPath()) == 0 {
|
||||||
return layout.Dimensions{}
|
return layout.Dimensions{}
|
||||||
}
|
}
|
||||||
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||||
|
|||||||
Reference in New Issue
Block a user