From 1468bd5c5a2f13d6079a35513902fd8492a95b4b Mon Sep 17 00:00:00 2001 From: Joe Julian Date: Wed, 1 Apr 2026 16:43:15 -0700 Subject: [PATCH] Improve recent targets and workflow context --- main.go | 24 +++++++++++- ui_api.go | 12 ++++++ ui_forms.go | 111 ++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 134 insertions(+), 13 deletions(-) diff --git a/main.go b/main.go index 1c28fc9..d277d1c 100644 --- a/main.go +++ b/main.go @@ -2974,11 +2974,31 @@ func (u *ui) detailPanelContent(gtx layout.Context) layout.Dimensions { } rows := []layout.Widget{ func(gtx layout.Context) layout.Dimensions { - lbl := material.Label(u.theme, titleSize, item.Title) + title := item.Title + if u.state.Section == appstate.SectionRecycleBin { + title = "Deleted: " + title + } + lbl := material.Label(u.theme, titleSize, title) lbl.Color = accentColor return lbl.Layout(gtx) }, layout.Spacer{Height: titlePad}.Layout, + func(gtx layout.Context) layout.Dimensions { + if u.state.Section != appstate.SectionRecycleBin { + return layout.Dimensions{} + } + return compactCard(gtx, func(gtx layout.Context) layout.Dimensions { + lbl := material.Label(u.theme, unit.Sp(12), "This entry is in the recycle bin. Review it, copy from it, or restore it back into the vault.") + lbl.Color = mutedColor + return lbl.Layout(gtx) + }) + }, + layout.Spacer{Height: func() unit.Dp { + if u.state.Section == appstate.SectionRecycleBin { + return unit.Dp(8) + } + return 0 + }()}.Layout, detailLine(u.theme, "Path", strings.Join(u.displayEntryPath(item.Path), " / ")), layout.Spacer{Height: sectionGap}.Layout, detailLine(u.theme, "Username", item.Username), @@ -3062,7 +3082,7 @@ func (u *ui) detailPanelContent(gtx layout.Context) layout.Dimensions { }), ) case appstate.SectionRecycleBin: - return tonedButton(gtx, u.theme, &u.restoreEntry, "Restore Entry") + return tonedButton(gtx, u.theme, &u.restoreEntry, "Restore Entry To Vault") default: return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { diff --git a/ui_api.go b/ui_api.go index c124362..2560e93 100644 --- a/ui_api.go +++ b/ui_api.go @@ -650,6 +650,12 @@ func (u *ui) apiTokenDetailPanel(gtx layout.Context) layout.Dimensions { return lbl.Layout(gtx) }, layout.Spacer{Height: unit.Dp(6)}.Layout, + func(gtx layout.Context) layout.Dimensions { + lbl := material.Label(u.theme, unit.Sp(11), "Rules are evaluated per operation. Explicit deny rules override allow rules.") + lbl.Color = mutedColor + return lbl.Layout(gtx) + }, + layout.Spacer{Height: unit.Dp(6)}.Layout, func(gtx layout.Context) layout.Dimensions { return material.CheckBox(u.theme, &u.apiPolicyAllow, "Allow rule (unchecked means deny rule)").Layout(gtx) }, @@ -696,6 +702,12 @@ func (u *ui) apiAuditDetailPanel(gtx layout.Context) layout.Dimensions { event := events[u.selectedAuditIndex] rows = append(rows, layout.Spacer{Height: unit.Dp(12)}.Layout, + func(gtx layout.Context) layout.Dimensions { + lbl := material.Label(u.theme, unit.Sp(12), "Selected event") + lbl.Color = mutedColor + return lbl.Layout(gtx) + }, + layout.Spacer{Height: unit.Dp(6)}.Layout, func(gtx layout.Context) layout.Dimensions { return compactCard(gtx, func(gtx layout.Context) layout.Dimensions { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, diff --git a/ui_forms.go b/ui_forms.go index 7a02820..073221b 100644 --- a/ui_forms.go +++ b/ui_forms.go @@ -2,11 +2,14 @@ package main import ( "fmt" + "image" "image/color" "path/filepath" "strings" "gioui.org/layout" + "gioui.org/op/clip" + "gioui.org/op/paint" "gioui.org/unit" "gioui.org/widget" "gioui.org/widget/material" @@ -65,6 +68,48 @@ func (u *ui) lifecycleControls(gtx layout.Context) 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(layout.Spacer{Height: unit.Dp(6)}.Layout), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + if strings.TrimSpace(u.remoteBaseURL.Text()) == "" || strings.TrimSpace(u.remotePath.Text()) == "" { + return layout.Dimensions{} + } + record := recentRemoteRecord{ + BaseURL: strings.TrimSpace(u.remoteBaseURL.Text()), + Path: strings.TrimSpace(u.remotePath.Text()), + } + lastGroup := u.recentRemoteGroup(record.BaseURL, record.Path) + return compactCard(gtx, func(gtx layout.Context) layout.Dimensions { + return layout.UniformInset(unit.Dp(10)).Layout(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(12), "SELECTED CONNECTION") + lbl.Color = mutedColor + return lbl.Layout(gtx) + }), + layout.Rigid(layout.Spacer{Height: unit.Dp(2)}.Layout), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + lbl := material.Label(u.theme, unit.Sp(14), friendlyRecentRemoteLabel(record)) + lbl.Color = accentColor + return lbl.Layout(gtx) + }), + layout.Rigid(layout.Spacer{Height: unit.Dp(2)}.Layout), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + lbl := material.Label(u.theme, unit.Sp(11), record.BaseURL) + lbl.Color = mutedColor + return lbl.Layout(gtx) + }), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + if len(lastGroup) == 0 { + return layout.Dimensions{} + } + lbl := material.Label(u.theme, unit.Sp(11), "Last group: "+strings.Join(u.displayEntryPath(lastGroup), " / ")) + lbl.Color = mutedColor + return lbl.Layout(gtx) + }), + ) + }) + }) + }), + layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { box := material.CheckBox(u.theme, &u.rememberRemoteAuth, "Remember username and password") box.Color = accentColor @@ -204,8 +249,10 @@ func (u *ui) recentVaultList(gtx layout.Context) layout.Dimensions { if friendly := friendlyRecentVaultLabel(path); friendly != "" { label = friendly } + lastGroup := u.recentVaultGroup(path) + selected := strings.TrimSpace(u.vaultPath.Text()) == path return layout.Inset{Bottom: unit.Dp(6)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - return compactCard(gtx, func(gtx layout.Context) layout.Dimensions { + return recentSelectionCard(gtx, selected, func(gtx layout.Context) layout.Dimensions { return u.recentVaultClicks[i].Layout(gtx, func(gtx layout.Context) layout.Dimensions { return layout.UniformInset(unit.Dp(10)).Layout(gtx, func(gtx layout.Context) layout.Dimensions { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, @@ -220,6 +267,14 @@ func (u *ui) recentVaultList(gtx layout.Context) layout.Dimensions { lbl.Color = mutedColor return lbl.Layout(gtx) }), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + if len(lastGroup) == 0 { + return layout.Dimensions{} + } + lbl := material.Label(u.theme, unit.Sp(11), "Last group: "+strings.Join(u.displayEntryPath(lastGroup), " / ")) + lbl.Color = mutedColor + return lbl.Layout(gtx) + }), ) }) }) @@ -255,8 +310,9 @@ func (u *ui) recentRemoteList(gtx layout.Context) layout.Dimensions { return material.List(u.theme, &u.lifecycleList).Layout(gtx, len(u.recentRemotes), func(gtx layout.Context, i int) layout.Dimensions { record := u.recentRemotes[i] label := friendlyRecentRemoteLabel(record) + selected := strings.TrimSpace(u.remoteBaseURL.Text()) == record.BaseURL && strings.TrimSpace(u.remotePath.Text()) == record.Path return layout.Inset{Bottom: unit.Dp(6)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - return compactCard(gtx, func(gtx layout.Context) layout.Dimensions { + return recentSelectionCard(gtx, selected, func(gtx layout.Context) layout.Dimensions { return u.recentRemoteClicks[i].Layout(gtx, func(gtx layout.Context) layout.Dimensions { return layout.UniformInset(unit.Dp(10)).Layout(gtx, func(gtx layout.Context) layout.Dimensions { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, @@ -271,6 +327,14 @@ func (u *ui) recentRemoteList(gtx layout.Context) layout.Dimensions { lbl.Color = mutedColor return lbl.Layout(gtx) }), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + if len(record.LastGroup) == 0 { + return layout.Dimensions{} + } + lbl := material.Label(u.theme, unit.Sp(11), "Last group: "+strings.Join(u.displayEntryPath(record.LastGroup), " / ")) + lbl.Color = mutedColor + return lbl.Layout(gtx) + }), ) }) }) @@ -281,6 +345,29 @@ func (u *ui) recentRemoteList(gtx layout.Context) layout.Dimensions { ) } +func recentSelectionCard(gtx layout.Context, selected bool, w layout.Widget) layout.Dimensions { + if !selected { + return compactCard(gtx, w) + } + return layout.Stack{}.Layout(gtx, + layout.Expanded(func(gtx layout.Context) layout.Dimensions { + size := gtx.Constraints.Min + if size.X == 0 { + size.X = gtx.Constraints.Max.X + } + if size.Y == 0 { + size.Y = gtx.Constraints.Max.Y + } + paint.FillShape(gtx.Ops, selectedColor, clip.Rect{Max: size}.Op()) + paint.FillShape(gtx.Ops, selectedEdge, clip.Rect{Max: image.Pt(4, size.Y)}.Op()) + return layout.Dimensions{Size: size} + }), + layout.Stacked(func(gtx layout.Context) layout.Dimensions { + return layout.UniformInset(unit.Dp(10)).Layout(gtx, w) + }), + ) +} + func friendlyRecentVaultLabel(path string) string { value := strings.TrimSpace(path) if value == "" { @@ -492,16 +579,20 @@ func (u *ui) groupControls(gtx layout.Context) layout.Dimensions { return layout.Dimensions{} } if u.deleteGroupPendingConfirmation() { - lbl := material.Label(u.theme, unit.Sp(12), fmt.Sprintf("Delete %q? This group is empty, and the deletion cannot be undone.", strings.Join(u.displayPath(), " / "))) - lbl.Color = mutedColor - return lbl.Layout(gtx) + return compactCard(gtx, func(gtx layout.Context) layout.Dimensions { + lbl := material.Label(u.theme, unit.Sp(12), fmt.Sprintf("Delete %q? This group is empty, and the deletion cannot be undone.", strings.Join(u.displayPath(), " / "))) + lbl.Color = mutedColor + return lbl.Layout(gtx) + }) } if deletable || strings.TrimSpace(deleteReason) == "" { return layout.Dimensions{} } - lbl := material.Label(u.theme, unit.Sp(12), deleteReason) - lbl.Color = mutedColor - return lbl.Layout(gtx) + return compactCard(gtx, func(gtx layout.Context) layout.Dimensions { + lbl := material.Label(u.theme, unit.Sp(12), deleteReason) + lbl.Color = mutedColor + return lbl.Layout(gtx) + }) }), layout.Rigid(func(gtx layout.Context) layout.Dimensions { if !u.deleteGroupPendingConfirmation() { @@ -527,9 +618,7 @@ func (u *ui) groupControlsSection(gtx layout.Context) layout.Dimensions { if u.groupControlsHidden { return layout.Dimensions{} } - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(u.groupControls), - ) + return compactCard(gtx, u.groupControls) } func (u *ui) groupControlsDisclosure(gtx layout.Context) layout.Dimensions {