Tighten navigation and admin UI
This commit is contained in:
@@ -108,6 +108,7 @@ type recentRemoteRecord struct {
|
|||||||
type uiPreferences struct {
|
type uiPreferences struct {
|
||||||
GroupControlsHidden bool `json:"groupControlsHidden"`
|
GroupControlsHidden bool `json:"groupControlsHidden"`
|
||||||
LifecycleAdvancedHidden bool `json:"lifecycleAdvancedHidden"`
|
LifecycleAdvancedHidden bool `json:"lifecycleAdvancedHidden"`
|
||||||
|
HistoryHidden bool `json:"historyHidden"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type entriesSectionState struct {
|
type entriesSectionState struct {
|
||||||
@@ -222,6 +223,7 @@ type ui struct {
|
|||||||
addCustomField widget.Clickable
|
addCustomField widget.Clickable
|
||||||
toggleGroupControls widget.Clickable
|
toggleGroupControls widget.Clickable
|
||||||
toggleLifecycleAdvanced widget.Clickable
|
toggleLifecycleAdvanced widget.Clickable
|
||||||
|
toggleHistory widget.Clickable
|
||||||
togglePasswordInline widget.Clickable
|
togglePasswordInline widget.Clickable
|
||||||
toggleSyncPassword widget.Clickable
|
toggleSyncPassword widget.Clickable
|
||||||
showEntries widget.Clickable
|
showEntries widget.Clickable
|
||||||
@@ -304,6 +306,7 @@ type ui struct {
|
|||||||
editingEntry bool
|
editingEntry bool
|
||||||
groupControlsHidden bool
|
groupControlsHidden bool
|
||||||
lifecycleAdvancedHidden bool
|
lifecycleAdvancedHidden bool
|
||||||
|
historyHidden bool
|
||||||
recentVaults []string
|
recentVaults []string
|
||||||
recentRemotes []recentRemoteRecord
|
recentRemotes []recentRemoteRecord
|
||||||
recentVaultGroups map[string][]string
|
recentVaultGroups map[string][]string
|
||||||
@@ -423,6 +426,7 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths)
|
|||||||
recentVaultGroups: map[string][]string{},
|
recentVaultGroups: map[string][]string{},
|
||||||
recentVaultUsedAt: map[string]time.Time{},
|
recentVaultUsedAt: map[string]time.Time{},
|
||||||
lifecycleAdvancedHidden: true,
|
lifecycleAdvancedHidden: true,
|
||||||
|
historyHidden: true,
|
||||||
now: time.Now,
|
now: time.Now,
|
||||||
syncSourceMode: syncSourceLocal,
|
syncSourceMode: syncSourceLocal,
|
||||||
syncDirection: syncDirectionPull,
|
syncDirection: syncDirectionPull,
|
||||||
@@ -1105,6 +1109,7 @@ func (u *ui) loadUIPreferences() {
|
|||||||
}
|
}
|
||||||
u.groupControlsHidden = prefs.GroupControlsHidden
|
u.groupControlsHidden = prefs.GroupControlsHidden
|
||||||
u.lifecycleAdvancedHidden = prefs.LifecycleAdvancedHidden
|
u.lifecycleAdvancedHidden = prefs.LifecycleAdvancedHidden
|
||||||
|
u.historyHidden = prefs.HistoryHidden
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) saveUIPreferences() {
|
func (u *ui) saveUIPreferences() {
|
||||||
@@ -1117,6 +1122,7 @@ func (u *ui) saveUIPreferences() {
|
|||||||
content, err := json.MarshalIndent(uiPreferences{
|
content, err := json.MarshalIndent(uiPreferences{
|
||||||
GroupControlsHidden: u.groupControlsHidden,
|
GroupControlsHidden: u.groupControlsHidden,
|
||||||
LifecycleAdvancedHidden: u.lifecycleAdvancedHidden,
|
LifecycleAdvancedHidden: u.lifecycleAdvancedHidden,
|
||||||
|
HistoryHidden: u.historyHidden,
|
||||||
}, "", " ")
|
}, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -1959,6 +1965,10 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
|
|||||||
u.groupControlsHidden = !u.groupControlsHidden
|
u.groupControlsHidden = !u.groupControlsHidden
|
||||||
u.saveUIPreferences()
|
u.saveUIPreferences()
|
||||||
}
|
}
|
||||||
|
for u.toggleHistory.Clicked(gtx) {
|
||||||
|
u.historyHidden = !u.historyHidden
|
||||||
|
u.saveUIPreferences()
|
||||||
|
}
|
||||||
for u.renameGroup.Clicked(gtx) {
|
for u.renameGroup.Clicked(gtx) {
|
||||||
u.clearDeleteGroupConfirmation()
|
u.clearDeleteGroupConfirmation()
|
||||||
u.runAction("rename group", u.renameGroupAction)
|
u.runAction("rename group", u.renameGroupAction)
|
||||||
@@ -2600,6 +2610,20 @@ func (u *ui) listPanel(gtx layout.Context) layout.Dimensions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) navigationHeader(gtx layout.Context) layout.Dimensions {
|
func (u *ui) navigationHeader(gtx layout.Context) layout.Dimensions {
|
||||||
|
if u.mode == "phone" {
|
||||||
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return u.sectionBar(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
if u.state.Section != appstate.SectionEntries {
|
||||||
|
return layout.Dimensions{}
|
||||||
|
}
|
||||||
|
return u.groupControlsDisclosure(gtx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
||||||
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
||||||
return u.sectionBar(gtx)
|
return u.sectionBar(gtx)
|
||||||
@@ -2614,21 +2638,58 @@ func (u *ui) navigationHeader(gtx layout.Context) layout.Dimensions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) sectionBar(gtx layout.Context) layout.Dimensions {
|
func (u *ui) sectionBar(gtx layout.Context) layout.Dimensions {
|
||||||
|
tabs := []struct {
|
||||||
|
click *widget.Clickable
|
||||||
|
label string
|
||||||
|
active bool
|
||||||
|
}{
|
||||||
|
{click: &u.showEntries, label: "Entries", active: u.state.Section == appstate.SectionEntries},
|
||||||
|
{click: &u.showRecycle, label: "Recycle Bin", active: u.state.Section == appstate.SectionRecycleBin},
|
||||||
|
{click: &u.showAPITokens, label: "API Tokens", active: u.state.Section == appstate.SectionAPITokens},
|
||||||
|
{click: &u.showAPIAudit, label: "API Audit", active: u.state.Section == appstate.SectionAPIAudit},
|
||||||
|
}
|
||||||
|
if u.mode == "phone" {
|
||||||
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return sectionTabButton(gtx, u.theme, tabs[0].click, tabs[0].label, tabs[0].active)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return sectionTabButton(gtx, u.theme, tabs[1].click, tabs[1].label, tabs[1].active)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
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 {
|
||||||
|
return sectionTabButton(gtx, u.theme, tabs[2].click, tabs[2].label, tabs[2].active)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return sectionTabButton(gtx, u.theme, tabs[3].click, tabs[3].label, tabs[3].active)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
return sectionTabButton(gtx, u.theme, &u.showEntries, "Entries", u.state.Section == appstate.SectionEntries)
|
return sectionTabButton(gtx, u.theme, tabs[0].click, tabs[0].label, tabs[0].active)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
return sectionTabButton(gtx, u.theme, &u.showRecycle, "Recycle Bin", u.state.Section == appstate.SectionRecycleBin)
|
return sectionTabButton(gtx, u.theme, tabs[1].click, tabs[1].label, tabs[1].active)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
return sectionTabButton(gtx, u.theme, &u.showAPITokens, "API Tokens", u.state.Section == appstate.SectionAPITokens)
|
return sectionTabButton(gtx, u.theme, tabs[2].click, tabs[2].label, tabs[2].active)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
return sectionTabButton(gtx, u.theme, &u.showAPIAudit, "API Audit", u.state.Section == appstate.SectionAPIAudit)
|
return sectionTabButton(gtx, u.theme, tabs[3].click, tabs[3].label, tabs[3].active)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -2780,7 +2841,12 @@ func (u *ui) detailPanel(gtx layout.Context) layout.Dimensions {
|
|||||||
layout.Rigid(u.syncButtonGroup),
|
layout.Rigid(u.syncButtonGroup),
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &u.openSecuritySettings, "Security")
|
btn := material.IconButton(u.theme, &u.openSecuritySettings, u.settingsIcon, "Vault settings")
|
||||||
|
btn.Background = selectedColor
|
||||||
|
btn.Color = accentColor
|
||||||
|
btn.Size = unit.Dp(18)
|
||||||
|
btn.Inset = layout.UniformInset(unit.Dp(8))
|
||||||
|
return btn.Layout(gtx)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
@@ -2893,7 +2959,13 @@ func (u *ui) detailPanelContent(gtx layout.Context) layout.Dimensions {
|
|||||||
detailLine(u.theme, "URL", item.URL),
|
detailLine(u.theme, "URL", item.URL),
|
||||||
layout.Spacer{Height: sectionGap}.Layout,
|
layout.Spacer{Height: sectionGap}.Layout,
|
||||||
detailLine(u.theme, "Tags", strings.Join(item.Tags, ", ")),
|
detailLine(u.theme, "Tags", strings.Join(item.Tags, ", ")),
|
||||||
layout.Spacer{Height: unit.Dp(12)}.Layout,
|
layout.Spacer{Height: unit.Dp(10)}.Layout,
|
||||||
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), "Quick actions")
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
},
|
||||||
|
layout.Spacer{Height: unit.Dp(6)}.Layout,
|
||||||
func(gtx layout.Context) layout.Dimensions {
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
if u.mode == "phone" {
|
if u.mode == "phone" {
|
||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
@@ -2928,12 +3000,20 @@ func (u *ui) detailPanelContent(gtx layout.Context) layout.Dimensions {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
layout.Spacer{Height: unit.Dp(12)}.Layout,
|
layout.Spacer{Height: unit.Dp(12)}.Layout,
|
||||||
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), "Notes")
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
},
|
||||||
|
layout.Spacer{Height: unit.Dp(4)}.Layout,
|
||||||
func(gtx layout.Context) layout.Dimensions {
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
lbl := material.Body1(u.theme, item.Notes)
|
lbl := material.Body1(u.theme, item.Notes)
|
||||||
lbl.Color = mutedColor
|
lbl.Color = mutedColor
|
||||||
return lbl.Layout(gtx)
|
return lbl.Layout(gtx)
|
||||||
},
|
},
|
||||||
layout.Spacer{Height: unit.Dp(12)}.Layout,
|
layout.Spacer{Height: unit.Dp(12)}.Layout,
|
||||||
|
u.attachmentSummaryPanel,
|
||||||
|
layout.Spacer{Height: unit.Dp(12)}.Layout,
|
||||||
u.historyPanel,
|
u.historyPanel,
|
||||||
layout.Spacer{Height: unit.Dp(12)}.Layout,
|
layout.Spacer{Height: unit.Dp(12)}.Layout,
|
||||||
func(gtx layout.Context) layout.Dimensions {
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
@@ -3013,12 +3093,42 @@ func (u *ui) historyPanel(gtx layout.Context) layout.Dimensions {
|
|||||||
|
|
||||||
children := []layout.FlexChild{
|
children := []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(14), "History")
|
return u.toggleHistory.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
lbl.Color = accentColor
|
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
||||||
return lbl.Layout(gtx)
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
icon := u.expandLessIcon
|
||||||
|
if u.historyHidden {
|
||||||
|
icon = u.expandMoreIcon
|
||||||
|
}
|
||||||
|
if icon != nil {
|
||||||
|
return icon.Layout(gtx, accentColor)
|
||||||
|
}
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(16), ">")
|
||||||
|
if !u.historyHidden {
|
||||||
|
lbl.Text = "v"
|
||||||
|
}
|
||||||
|
lbl.Color = accentColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Width: unit.Dp(4)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(14), "History")
|
||||||
|
lbl.Color = accentColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||||
}
|
}
|
||||||
|
if u.historyHidden {
|
||||||
|
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), fmt.Sprintf("%d saved version(s).", len(history)))
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}))
|
||||||
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, children...)
|
||||||
|
}
|
||||||
|
|
||||||
if len(history) == 0 {
|
if len(history) == 0 {
|
||||||
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
@@ -3063,6 +3173,40 @@ func (u *ui) historyPanel(gtx layout.Context) layout.Dimensions {
|
|||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, children...)
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, children...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ui) attachmentSummaryPanel(gtx layout.Context) layout.Dimensions {
|
||||||
|
items := u.selectedAttachmentItems()
|
||||||
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), "Attachments")
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
if len(items) == 0 {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), "No attachments on this entry.")
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}
|
||||||
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, func() []layout.FlexChild {
|
||||||
|
children := make([]layout.FlexChild, 0, len(items)*2)
|
||||||
|
for i, item := range items {
|
||||||
|
label := fmt.Sprintf("%s (%d B)", item.Name, item.Size)
|
||||||
|
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), label)
|
||||||
|
lbl.Color = accentColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}))
|
||||||
|
if i < len(items)-1 {
|
||||||
|
children = append(children, layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
}()...)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ui) historyRow(gtx layout.Context, click *widget.Clickable, index int, item entry) layout.Dimensions {
|
func (u *ui) historyRow(gtx layout.Context, click *widget.Clickable, index int, item entry) layout.Dimensions {
|
||||||
for click.Clicked(gtx) {
|
for click.Clicked(gtx) {
|
||||||
_ = u.selectHistoryVersion(index)
|
_ = u.selectHistoryVersion(index)
|
||||||
@@ -3128,10 +3272,11 @@ func (u *ui) pathBar(gtx layout.Context) layout.Dimensions {
|
|||||||
|
|
||||||
u.syncCurrentPath()
|
u.syncCurrentPath()
|
||||||
displayPath := u.displayPath()
|
displayPath := u.displayPath()
|
||||||
crumbs := append([]string{"/"}, append([]string{}, displayPath...)...)
|
pathSource := displayPath
|
||||||
if u.state.Section == appstate.SectionTemplates {
|
if u.state.Section == appstate.SectionTemplates {
|
||||||
crumbs = append([]string{"Templates"}, append([]string{}, u.currentPath...)...)
|
pathSource = append([]string{}, u.currentPath...)
|
||||||
}
|
}
|
||||||
|
crumbs, indices := u.visibleBreadcrumbs(pathSource)
|
||||||
return layout.Flex{Alignment: layout.Middle}.Layout(gtx, func() []layout.FlexChild {
|
return layout.Flex{Alignment: layout.Middle}.Layout(gtx, func() []layout.FlexChild {
|
||||||
children := make([]layout.FlexChild, 0, len(crumbs)*2)
|
children := make([]layout.FlexChild, 0, len(crumbs)*2)
|
||||||
for i, name := range crumbs {
|
for i, name := range crumbs {
|
||||||
@@ -3139,7 +3284,8 @@ func (u *ui) pathBar(gtx layout.Context) layout.Dimensions {
|
|||||||
label := name
|
label := name
|
||||||
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 {
|
target := indices[index]
|
||||||
|
if target == 0 {
|
||||||
root := u.hiddenVaultRoot()
|
root := u.hiddenVaultRoot()
|
||||||
if root == "" {
|
if root == "" {
|
||||||
u.setCurrentPath(nil)
|
u.setCurrentPath(nil)
|
||||||
@@ -3147,7 +3293,7 @@ func (u *ui) pathBar(gtx layout.Context) layout.Dimensions {
|
|||||||
u.setCurrentPath([]string{root})
|
u.setCurrentPath([]string{root})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
nextPath := crumbs[1 : index+1]
|
nextPath := pathSource[:target]
|
||||||
root := u.hiddenVaultRoot()
|
root := u.hiddenVaultRoot()
|
||||||
if root != "" {
|
if root != "" {
|
||||||
nextPath = append([]string{root}, nextPath...)
|
nextPath = append([]string{root}, nextPath...)
|
||||||
@@ -3159,7 +3305,12 @@ func (u *ui) pathBar(gtx layout.Context) layout.Dimensions {
|
|||||||
btn := material.Button(u.theme, &u.breadcrumbs[index], label)
|
btn := material.Button(u.theme, &u.breadcrumbs[index], label)
|
||||||
btn.Background, btn.Color = buttonFocusColors(u.isFocused(breadcrumbFocusID(index)))
|
btn.Background, btn.Color = buttonFocusColors(u.isFocused(breadcrumbFocusID(index)))
|
||||||
btn.TextSize = unit.Sp(11)
|
btn.TextSize = unit.Sp(11)
|
||||||
btn.Inset = layout.Inset{Top: 5, Bottom: 5, Left: 9, Right: 9}
|
if u.mode == "phone" {
|
||||||
|
btn.TextSize = unit.Sp(10)
|
||||||
|
btn.Inset = layout.Inset{Top: 4, Bottom: 4, Left: 7, Right: 7}
|
||||||
|
} else {
|
||||||
|
btn.Inset = layout.Inset{Top: 5, Bottom: 5, Left: 9, Right: 9}
|
||||||
|
}
|
||||||
return btn.Layout(gtx)
|
return btn.Layout(gtx)
|
||||||
}))
|
}))
|
||||||
if i < len(crumbs)-1 {
|
if i < len(crumbs)-1 {
|
||||||
@@ -3174,6 +3325,31 @@ func (u *ui) pathBar(gtx layout.Context) layout.Dimensions {
|
|||||||
}()...)
|
}()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ui) visibleBreadcrumbs(displayPath []string) ([]string, []int) {
|
||||||
|
if u.state.Section == appstate.SectionTemplates {
|
||||||
|
return append([]string{"Templates"}, append([]string{}, u.currentPath...)...), func() []int {
|
||||||
|
indices := make([]int, 0, len(u.currentPath)+1)
|
||||||
|
indices = append(indices, 0)
|
||||||
|
for i := range u.currentPath {
|
||||||
|
indices = append(indices, i+1)
|
||||||
|
}
|
||||||
|
return indices
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if u.mode != "phone" || len(displayPath) <= 2 {
|
||||||
|
crumbs := append([]string{"/"}, append([]string{}, displayPath...)...)
|
||||||
|
indices := make([]int, 0, len(crumbs))
|
||||||
|
indices = append(indices, 0)
|
||||||
|
for i := range displayPath {
|
||||||
|
indices = append(indices, i+1)
|
||||||
|
}
|
||||||
|
return crumbs, indices
|
||||||
|
}
|
||||||
|
crumbs := []string{"/", "…", displayPath[len(displayPath)-2], displayPath[len(displayPath)-1]}
|
||||||
|
indices := []int{0, len(displayPath) - 2, len(displayPath) - 1, len(displayPath)}
|
||||||
|
return crumbs, indices
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ui) groupBar(gtx layout.Context) layout.Dimensions {
|
func (u *ui) groupBar(gtx layout.Context) layout.Dimensions {
|
||||||
groups := append([]string{}, u.childGroups()...)
|
groups := append([]string{}, u.childGroups()...)
|
||||||
if len(u.groupClicks) < len(groups) {
|
if len(u.groupClicks) < len(groups) {
|
||||||
@@ -3185,14 +3361,21 @@ func (u *ui) groupBar(gtx layout.Context) layout.Dimensions {
|
|||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, func() []layout.FlexChild {
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, func() []layout.FlexChild {
|
||||||
children := []layout.FlexChild{
|
children := []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(12), "Groups")
|
label := "Groups"
|
||||||
|
if len(u.displayPath()) > 0 {
|
||||||
|
label = "Subgroups"
|
||||||
|
}
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), label)
|
||||||
lbl.Color = mutedColor
|
lbl.Color = mutedColor
|
||||||
return lbl.Layout(gtx)
|
return lbl.Layout(gtx)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
}
|
}
|
||||||
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
const maxGroupListHeight = 160
|
maxGroupListHeight := 200
|
||||||
|
if u.mode == "phone" {
|
||||||
|
maxGroupListHeight = 112
|
||||||
|
}
|
||||||
maxY := gtx.Dp(unit.Dp(maxGroupListHeight))
|
maxY := gtx.Dp(unit.Dp(maxGroupListHeight))
|
||||||
if gtx.Constraints.Max.Y > maxY {
|
if gtx.Constraints.Max.Y > maxY {
|
||||||
gtx.Constraints.Max.Y = maxY
|
gtx.Constraints.Max.Y = maxY
|
||||||
|
|||||||
@@ -293,16 +293,16 @@ func (u *ui) apiAuditEvents() []apiaudit.Event {
|
|||||||
return filtered
|
return filtered
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatAPIPolicyRule(rule apitokens.PolicyRule) string {
|
func policyRuleParts(rule apitokens.PolicyRule) (string, string, string) {
|
||||||
scope := strings.Join(rule.Resource.Path, " / ")
|
effect := strings.ToUpper(string(rule.Effect))
|
||||||
|
operation := string(rule.Operation)
|
||||||
|
resource := "/"
|
||||||
if rule.Resource.Kind == apitokens.ResourceEntry {
|
if rule.Resource.Kind == apitokens.ResourceEntry {
|
||||||
scope = "entry " + rule.Resource.EntryID
|
resource = "Entry: " + rule.Resource.EntryID
|
||||||
|
} else if len(rule.Resource.Path) > 0 {
|
||||||
|
resource = strings.Join(rule.Resource.Path, " / ")
|
||||||
}
|
}
|
||||||
return strings.TrimSpace(strings.Join([]string{
|
return effect, operation, resource
|
||||||
strings.ToUpper(string(rule.Effect)),
|
|
||||||
string(rule.Operation),
|
|
||||||
scope,
|
|
||||||
}, " "))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func uiHasPolicyRule(rules []apitokens.PolicyRule, target apitokens.PolicyRule) bool {
|
func uiHasPolicyRule(rules []apitokens.PolicyRule, target apitokens.PolicyRule) bool {
|
||||||
@@ -346,6 +346,12 @@ func (u *ui) apiTokenRow(gtx layout.Context, click *widget.Clickable, idx int, t
|
|||||||
if token.ExpiresAt != nil {
|
if token.ExpiresAt != nil {
|
||||||
text = "Expires " + token.ExpiresAt.Local().Format(time.RFC3339)
|
text = "Expires " + token.ExpiresAt.Local().Format(time.RFC3339)
|
||||||
}
|
}
|
||||||
|
if token.Disabled {
|
||||||
|
text = "Disabled · " + text
|
||||||
|
}
|
||||||
|
if token.RevokedAt != nil {
|
||||||
|
text = "Revoked · " + text
|
||||||
|
}
|
||||||
lbl := material.Label(u.theme, unit.Sp(12), text)
|
lbl := material.Label(u.theme, unit.Sp(12), text)
|
||||||
lbl.Color = mutedColor
|
lbl.Color = mutedColor
|
||||||
return lbl.Layout(gtx)
|
return lbl.Layout(gtx)
|
||||||
@@ -392,6 +398,11 @@ func (u *ui) apiAuditRow(gtx layout.Context, click *widget.Clickable, idx int, e
|
|||||||
lbl.Color = accentColor
|
lbl.Color = accentColor
|
||||||
return lbl.Layout(gtx)
|
return lbl.Layout(gtx)
|
||||||
}),
|
}),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), string(event.Operation))
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
lbl := material.Label(u.theme, unit.Sp(12), event.At.Local().Format(time.RFC3339))
|
lbl := material.Label(u.theme, unit.Sp(12), event.At.Local().Format(time.RFC3339))
|
||||||
lbl.Color = mutedColor
|
lbl.Color = mutedColor
|
||||||
@@ -441,9 +452,15 @@ func (u *ui) apiAuditListPanel(gtx layout.Context) layout.Dimensions {
|
|||||||
return lbl.Layout(gtx)
|
return lbl.Layout(gtx)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), "Filter by typing a token name, decision, operation, or resource in Search vault.")
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||||
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
||||||
if len(events) == 0 {
|
if len(events) == 0 {
|
||||||
lbl := material.Label(u.theme, unit.Sp(14), "No audit events yet.")
|
lbl := material.Label(u.theme, unit.Sp(14), "No audit events yet. Approval prompts, denials, token changes, and filled requests will appear here.")
|
||||||
lbl.Color = mutedColor
|
lbl.Color = mutedColor
|
||||||
return lbl.Layout(gtx)
|
return lbl.Layout(gtx)
|
||||||
}
|
}
|
||||||
@@ -520,25 +537,33 @@ func (u *ui) apiTokenDetailPanel(gtx layout.Context) layout.Dimensions {
|
|||||||
rows = append(rows,
|
rows = append(rows,
|
||||||
layout.Spacer{Height: unit.Dp(10)}.Layout,
|
layout.Spacer{Height: unit.Dp(10)}.Layout,
|
||||||
func(gtx layout.Context) layout.Dimensions {
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &u.saveAPIToken, "Save Token")
|
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.saveAPIToken, "Save Token")
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.rotateAPIToken, "Rotate")
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.disableAPIToken, "Disable")
|
||||||
|
}),
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &u.rotateAPIToken, "Rotate")
|
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||||
}),
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
return tonedButton(gtx, u.theme, &u.revokeAPIToken, "Revoke")
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
}),
|
||||||
return tonedButton(gtx, u.theme, &u.disableAPIToken, "Disable")
|
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||||
}),
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
return tonedButton(gtx, u.theme, &u.deleteAPIToken, "Delete")
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
}),
|
||||||
return tonedButton(gtx, u.theme, &u.revokeAPIToken, "Revoke")
|
)
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.deleteAPIToken, "Delete")
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -553,21 +578,38 @@ func (u *ui) apiTokenDetailPanel(gtx layout.Context) layout.Dimensions {
|
|||||||
if ok && len(token.Policies) > 0 {
|
if ok && len(token.Policies) > 0 {
|
||||||
for i, rule := range token.Policies {
|
for i, rule := range token.Policies {
|
||||||
index := i
|
index := i
|
||||||
ruleText := formatAPIPolicyRule(rule)
|
effect, operation, resource := policyRuleParts(rule)
|
||||||
rows = append(rows,
|
rows = append(rows,
|
||||||
func(gtx layout.Context) layout.Dimensions {
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
lbl := material.Label(u.theme, unit.Sp(13), ruleText)
|
|
||||||
lbl.Color = mutedColor
|
|
||||||
return lbl.Layout(gtx)
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &removeClicks[index], "Remove")
|
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), effect)
|
||||||
|
lbl.Color = accentColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
||||||
|
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), operation)
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &removeClicks[index], "Remove")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
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), resource)
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
},
|
})
|
||||||
|
},
|
||||||
layout.Spacer{Height: unit.Dp(6)}.Layout,
|
layout.Spacer{Height: unit.Dp(6)}.Layout,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+126
-27
@@ -36,9 +36,22 @@ func (u *ui) lifecycleControls(gtx layout.Context) layout.Dimensions {
|
|||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
if u.lifecycleMode == "remote" {
|
if u.lifecycleMode == "remote" {
|
||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), "LOCATION")
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout),
|
||||||
layout.Rigid(labeledEditorHelp(u.theme, "Remote Base URL", "Base WebDAV endpoint, for example https://server/remote.php/webdav.", &u.remoteBaseURL, false)),
|
layout.Rigid(labeledEditorHelp(u.theme, "Remote Base URL", "Base WebDAV endpoint, for example https://server/remote.php/webdav.", &u.remoteBaseURL, false)),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(labeledEditorHelp(u.theme, "Remote Path", "Path to the remote .kdbx file under the WebDAV base URL.", &u.remotePath, false)),
|
layout.Rigid(labeledEditorHelp(u.theme, "Remote Path", "Path to the remote .kdbx file under the WebDAV base URL.", &u.remotePath, false)),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), "AUTHENTICATION")
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(labeledEditorHelp(u.theme, "Remote Username", "Username used to authenticate to the WebDAV server.", &u.remoteUsername, false)),
|
layout.Rigid(labeledEditorHelp(u.theme, "Remote Username", "Username used to authenticate to the WebDAV server.", &u.remoteUsername, false)),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
@@ -57,6 +70,35 @@ 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(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
if strings.TrimSpace(u.vaultPath.Text()) == "" {
|
||||||
|
return layout.Dimensions{}
|
||||||
|
}
|
||||||
|
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 VAULT")
|
||||||
|
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), friendlyRecentVaultLabel(u.vaultPath.Text()))
|
||||||
|
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), u.vaultPath.Text())
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||||
layout.Rigid(u.recentVaultList),
|
layout.Rigid(u.recentVaultList),
|
||||||
)
|
)
|
||||||
@@ -146,7 +188,25 @@ func (u *ui) recentVaultList(gtx layout.Context) layout.Dimensions {
|
|||||||
label = friendly
|
label = friendly
|
||||||
}
|
}
|
||||||
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &u.recentVaultClicks[index], label)
|
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return u.recentVaultClicks[index].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,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(14), label)
|
||||||
|
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), path)
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
}))
|
}))
|
||||||
if i < len(u.recentVaults)-1 {
|
if i < len(u.recentVaults)-1 {
|
||||||
children = append(children, layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout))
|
children = append(children, layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout))
|
||||||
@@ -179,7 +239,25 @@ func (u *ui) recentRemoteList(gtx layout.Context) layout.Dimensions {
|
|||||||
index := i
|
index := i
|
||||||
label := friendlyRecentRemoteLabel(record)
|
label := friendlyRecentRemoteLabel(record)
|
||||||
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &u.recentRemoteClicks[index], label)
|
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return u.recentRemoteClicks[index].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,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(14), label)
|
||||||
|
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), strings.TrimSpace(record.BaseURL))
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
}))
|
}))
|
||||||
if i < len(u.recentRemotes)-1 {
|
if i < len(u.recentRemotes)-1 {
|
||||||
children = append(children, layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout))
|
children = append(children, layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout))
|
||||||
@@ -288,7 +366,7 @@ func (u *ui) customFieldEditorPanel(gtx layout.Context) layout.Dimensions {
|
|||||||
if len(u.customFieldKeys) == 1 && (strings.TrimSpace(u.customFieldKeys[index].Text()) != "" || strings.TrimSpace(u.customFieldValues[index].Text()) != "") {
|
if len(u.customFieldKeys) == 1 && (strings.TrimSpace(u.customFieldKeys[index].Text()) != "" || strings.TrimSpace(u.customFieldValues[index].Text()) != "") {
|
||||||
return layout.Dimensions{}
|
return layout.Dimensions{}
|
||||||
}
|
}
|
||||||
return tonedButton(gtx, u.theme, &u.removeCustomFields[index], "-")
|
return tonedButton(gtx, u.theme, &u.removeCustomFields[index], "Remove")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
@@ -304,7 +382,7 @@ func (u *ui) customFieldEditorPanel(gtx layout.Context) layout.Dimensions {
|
|||||||
for u.addCustomField.Clicked(gtx) {
|
for u.addCustomField.Clicked(gtx) {
|
||||||
u.appendCustomFieldRow("", "")
|
u.appendCustomFieldRow("", "")
|
||||||
}
|
}
|
||||||
return tonedButton(gtx, u.theme, &u.addCustomField, "+")
|
return tonedButton(gtx, u.theme, &u.addCustomField, "Add Custom Field")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -416,36 +494,44 @@ func (u *ui) groupControlsSection(gtx layout.Context) layout.Dimensions {
|
|||||||
|
|
||||||
func (u *ui) groupControlsDisclosure(gtx layout.Context) layout.Dimensions {
|
func (u *ui) groupControlsDisclosure(gtx layout.Context) layout.Dimensions {
|
||||||
return u.toggleGroupControls.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
return u.toggleGroupControls.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
return layout.UniformInset(unit.Dp(2)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
return layout.UniformInset(unit.Dp(6)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
||||||
icon := u.expandLessIcon
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
if u.groupControlsHidden {
|
icon := u.expandLessIcon
|
||||||
icon = u.expandMoreIcon
|
if u.groupControlsHidden {
|
||||||
}
|
icon = u.expandMoreIcon
|
||||||
if icon == nil {
|
|
||||||
lbl := material.Label(u.theme, unit.Sp(16), ">")
|
|
||||||
if !u.groupControlsHidden {
|
|
||||||
lbl.Text = "v"
|
|
||||||
}
|
}
|
||||||
lbl.Color = accentColor
|
if icon == nil {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(16), ">")
|
||||||
|
if !u.groupControlsHidden {
|
||||||
|
lbl.Text = "v"
|
||||||
|
}
|
||||||
|
lbl.Color = accentColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}
|
||||||
|
return icon.Layout(gtx, accentColor)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Width: unit.Dp(4)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), "Group tools")
|
||||||
|
lbl.Color = mutedColor
|
||||||
return lbl.Layout(gtx)
|
return lbl.Layout(gtx)
|
||||||
}
|
}),
|
||||||
return icon.Layout(gtx, accentColor)
|
)
|
||||||
}),
|
})
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(4)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
lbl := material.Label(u.theme, unit.Sp(12), "Group Tools")
|
|
||||||
lbl.Color = mutedColor
|
|
||||||
return lbl.Layout(gtx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) entryEditorPanel(gtx layout.Context) layout.Dimensions {
|
func (u *ui) entryEditorPanel(gtx layout.Context) layout.Dimensions {
|
||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), "BASICS")
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(labeledEditorWithFocus(u.theme, "Title", &u.entryTitle, false, u.isFocused(detailFocusID(detailFieldTitle)))),
|
layout.Rigid(labeledEditorWithFocus(u.theme, "Title", &u.entryTitle, false, u.isFocused(detailFocusID(detailFieldTitle)))),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(labeledEditorWithFocus(u.theme, "Username", &u.entryUsername, false, u.isFocused(detailFocusID(detailFieldUsername)))),
|
layout.Rigid(labeledEditorWithFocus(u.theme, "Username", &u.entryUsername, false, u.isFocused(detailFocusID(detailFieldUsername)))),
|
||||||
@@ -465,10 +551,23 @@ func (u *ui) entryEditorPanel(gtx layout.Context) layout.Dimensions {
|
|||||||
lbl.Color = mutedColor
|
lbl.Color = mutedColor
|
||||||
return lbl.Layout(gtx)
|
return lbl.Layout(gtx)
|
||||||
}),
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), "NOTES")
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(labeledMultilineEditorWithFocus(u.theme, "Notes", &u.entryNotes, false, u.isFocused(detailFocusID(detailFieldNotes)), unit.Dp(120))),
|
layout.Rigid(labeledMultilineEditorWithFocus(u.theme, "Notes", &u.entryNotes, false, u.isFocused(detailFocusID(detailFieldNotes)), unit.Dp(120))),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.Layout),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(u.customFieldEditorPanel),
|
layout.Rigid(u.customFieldEditorPanel),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), "HISTORY")
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||||
layout.Rigid(labeledEditorWithFocus(u.theme, "History Index", &u.historyIndex, false, u.isFocused(detailFocusID(detailFieldHistoryIndex)))),
|
layout.Rigid(labeledEditorWithFocus(u.theme, "History Index", &u.historyIndex, false, u.isFocused(detailFocusID(detailFieldHistoryIndex)))),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
@@ -508,7 +607,7 @@ func (u *ui) entryEditorPanel(gtx layout.Context) layout.Dimensions {
|
|||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.copyURL, "Copy URL") }),
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.copyURL, "Copy URL") }),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
lbl := material.Label(u.theme, unit.Sp(12), "ATTACHMENTS")
|
lbl := material.Label(u.theme, unit.Sp(12), "ATTACHMENTS")
|
||||||
lbl.Color = mutedColor
|
lbl.Color = mutedColor
|
||||||
|
|||||||
Reference in New Issue
Block a user