Simplify lifecycle and section UI
This commit is contained in:
@@ -29,9 +29,9 @@ import (
|
|||||||
"git.julianfamily.org/keepassgo/apiapproval"
|
"git.julianfamily.org/keepassgo/apiapproval"
|
||||||
"git.julianfamily.org/keepassgo/apiaudit"
|
"git.julianfamily.org/keepassgo/apiaudit"
|
||||||
"git.julianfamily.org/keepassgo/apitokens"
|
"git.julianfamily.org/keepassgo/apitokens"
|
||||||
|
"git.julianfamily.org/keepassgo/appstate"
|
||||||
keepassassets "git.julianfamily.org/keepassgo/assets"
|
keepassassets "git.julianfamily.org/keepassgo/assets"
|
||||||
"git.julianfamily.org/keepassgo/autofillcache"
|
"git.julianfamily.org/keepassgo/autofillcache"
|
||||||
"git.julianfamily.org/keepassgo/appstate"
|
|
||||||
"git.julianfamily.org/keepassgo/clipboard"
|
"git.julianfamily.org/keepassgo/clipboard"
|
||||||
"git.julianfamily.org/keepassgo/passwords"
|
"git.julianfamily.org/keepassgo/passwords"
|
||||||
"git.julianfamily.org/keepassgo/session"
|
"git.julianfamily.org/keepassgo/session"
|
||||||
@@ -106,7 +106,8 @@ type recentRemoteRecord struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type uiPreferences struct {
|
type uiPreferences struct {
|
||||||
GroupControlsHidden bool `json:"groupControlsHidden"`
|
GroupControlsHidden bool `json:"groupControlsHidden"`
|
||||||
|
LifecycleAdvancedHidden bool `json:"lifecycleAdvancedHidden"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type entriesSectionState struct {
|
type entriesSectionState struct {
|
||||||
@@ -131,189 +132,191 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ui struct {
|
type ui struct {
|
||||||
mode string
|
mode string
|
||||||
theme *material.Theme
|
theme *material.Theme
|
||||||
logoHorizontal paint.ImageOp
|
logoHorizontal paint.ImageOp
|
||||||
splashSquare paint.ImageOp
|
splashSquare paint.ImageOp
|
||||||
search widget.Editor
|
search widget.Editor
|
||||||
vaultPath widget.Editor
|
vaultPath widget.Editor
|
||||||
saveAsPath widget.Editor
|
saveAsPath widget.Editor
|
||||||
remoteBaseURL widget.Editor
|
remoteBaseURL widget.Editor
|
||||||
remotePath widget.Editor
|
remotePath widget.Editor
|
||||||
remoteUsername widget.Editor
|
remoteUsername widget.Editor
|
||||||
remotePassword widget.Editor
|
remotePassword widget.Editor
|
||||||
masterPassword widget.Editor
|
masterPassword widget.Editor
|
||||||
keyFilePath widget.Editor
|
keyFilePath widget.Editor
|
||||||
apiTokenName widget.Editor
|
apiTokenName widget.Editor
|
||||||
apiTokenClientName widget.Editor
|
apiTokenClientName widget.Editor
|
||||||
apiTokenExpiresAt widget.Editor
|
apiTokenExpiresAt widget.Editor
|
||||||
apiPolicyOperation widget.Editor
|
apiPolicyOperation widget.Editor
|
||||||
apiPolicyPath widget.Editor
|
apiPolicyPath widget.Editor
|
||||||
apiPolicyEntryID widget.Editor
|
apiPolicyEntryID widget.Editor
|
||||||
securityCipher widget.Editor
|
securityCipher widget.Editor
|
||||||
securityKDF widget.Editor
|
securityKDF widget.Editor
|
||||||
entryID widget.Editor
|
entryID widget.Editor
|
||||||
entryTitle widget.Editor
|
entryTitle widget.Editor
|
||||||
entryUsername widget.Editor
|
entryUsername widget.Editor
|
||||||
entryPassword widget.Editor
|
entryPassword widget.Editor
|
||||||
entryURL widget.Editor
|
entryURL widget.Editor
|
||||||
entryNotes widget.Editor
|
entryNotes widget.Editor
|
||||||
entryTags widget.Editor
|
entryTags widget.Editor
|
||||||
entryPath widget.Editor
|
entryPath widget.Editor
|
||||||
entryFields widget.Editor
|
entryFields widget.Editor
|
||||||
customFieldKeys []widget.Editor
|
customFieldKeys []widget.Editor
|
||||||
customFieldValues []widget.Editor
|
customFieldValues []widget.Editor
|
||||||
historyIndex widget.Editor
|
historyIndex widget.Editor
|
||||||
groupName widget.Editor
|
groupName widget.Editor
|
||||||
groupParentPath widget.Editor
|
groupParentPath widget.Editor
|
||||||
passwordProfile widget.Editor
|
passwordProfile widget.Editor
|
||||||
attachmentName widget.Editor
|
attachmentName widget.Editor
|
||||||
attachmentPath widget.Editor
|
attachmentPath widget.Editor
|
||||||
exportAttachmentPath widget.Editor
|
exportAttachmentPath widget.Editor
|
||||||
list widget.List
|
list widget.List
|
||||||
groupList widget.List
|
groupList widget.List
|
||||||
detailList widget.List
|
detailList widget.List
|
||||||
lifecycleList widget.List
|
lifecycleList widget.List
|
||||||
copyUser widget.Clickable
|
copyUser widget.Clickable
|
||||||
copyPass widget.Clickable
|
copyPass widget.Clickable
|
||||||
copyURL widget.Clickable
|
copyURL widget.Clickable
|
||||||
lockVault widget.Clickable
|
lockVault widget.Clickable
|
||||||
unlockVault widget.Clickable
|
unlockVault widget.Clickable
|
||||||
createVault widget.Clickable
|
createVault widget.Clickable
|
||||||
openVault widget.Clickable
|
openVault widget.Clickable
|
||||||
saveVault widget.Clickable
|
saveVault widget.Clickable
|
||||||
saveAsVault widget.Clickable
|
saveAsVault widget.Clickable
|
||||||
openRemote widget.Clickable
|
openRemote widget.Clickable
|
||||||
changeMasterKey widget.Clickable
|
changeMasterKey widget.Clickable
|
||||||
synchronizeVault widget.Clickable
|
synchronizeVault widget.Clickable
|
||||||
toggleSyncMenu widget.Clickable
|
toggleSyncMenu widget.Clickable
|
||||||
openAdvancedSync widget.Clickable
|
openAdvancedSync widget.Clickable
|
||||||
openSecuritySettings widget.Clickable
|
openSecuritySettings widget.Clickable
|
||||||
closeAdvancedSync widget.Clickable
|
closeAdvancedSync widget.Clickable
|
||||||
closeSecuritySettings widget.Clickable
|
closeSecuritySettings widget.Clickable
|
||||||
runAdvancedSync widget.Clickable
|
runAdvancedSync widget.Clickable
|
||||||
saveSecuritySettings widget.Clickable
|
saveSecuritySettings widget.Clickable
|
||||||
editEntry widget.Clickable
|
editEntry widget.Clickable
|
||||||
cancelEdit widget.Clickable
|
cancelEdit widget.Clickable
|
||||||
pickVaultPath widget.Clickable
|
pickVaultPath widget.Clickable
|
||||||
pickKeyFile widget.Clickable
|
pickKeyFile widget.Clickable
|
||||||
pickSyncLocalPath widget.Clickable
|
pickSyncLocalPath widget.Clickable
|
||||||
addEntry widget.Clickable
|
addEntry widget.Clickable
|
||||||
saveEntry widget.Clickable
|
saveEntry widget.Clickable
|
||||||
duplicateEntry widget.Clickable
|
duplicateEntry widget.Clickable
|
||||||
deleteEntry widget.Clickable
|
deleteEntry widget.Clickable
|
||||||
restoreEntry widget.Clickable
|
restoreEntry widget.Clickable
|
||||||
saveTemplate widget.Clickable
|
saveTemplate widget.Clickable
|
||||||
deleteTemplate widget.Clickable
|
deleteTemplate widget.Clickable
|
||||||
instantiateTemplate widget.Clickable
|
instantiateTemplate widget.Clickable
|
||||||
addAttachment widget.Clickable
|
addAttachment widget.Clickable
|
||||||
replaceAttachment widget.Clickable
|
replaceAttachment widget.Clickable
|
||||||
removeAttachment widget.Clickable
|
removeAttachment widget.Clickable
|
||||||
exportAttachment widget.Clickable
|
exportAttachment widget.Clickable
|
||||||
restoreHistory widget.Clickable
|
restoreHistory widget.Clickable
|
||||||
generatePassword widget.Clickable
|
generatePassword widget.Clickable
|
||||||
createGroup widget.Clickable
|
createGroup widget.Clickable
|
||||||
moveGroup widget.Clickable
|
moveGroup widget.Clickable
|
||||||
renameGroup widget.Clickable
|
renameGroup widget.Clickable
|
||||||
deleteGroup widget.Clickable
|
deleteGroup widget.Clickable
|
||||||
confirmDeleteGroup widget.Clickable
|
confirmDeleteGroup widget.Clickable
|
||||||
cancelDeleteGroup widget.Clickable
|
cancelDeleteGroup widget.Clickable
|
||||||
addCustomField widget.Clickable
|
addCustomField widget.Clickable
|
||||||
toggleGroupControls widget.Clickable
|
toggleGroupControls widget.Clickable
|
||||||
togglePasswordInline widget.Clickable
|
toggleLifecycleAdvanced widget.Clickable
|
||||||
toggleSyncPassword widget.Clickable
|
togglePasswordInline widget.Clickable
|
||||||
showEntries widget.Clickable
|
toggleSyncPassword widget.Clickable
|
||||||
showTemplates widget.Clickable
|
showEntries widget.Clickable
|
||||||
showRecycle widget.Clickable
|
showTemplates widget.Clickable
|
||||||
showAPITokens widget.Clickable
|
showRecycle widget.Clickable
|
||||||
showAPIAudit widget.Clickable
|
showAPITokens widget.Clickable
|
||||||
showLocalLifecycle widget.Clickable
|
showAPIAudit widget.Clickable
|
||||||
showRemoteLifecycle widget.Clickable
|
showLocalLifecycle widget.Clickable
|
||||||
showSyncLocal widget.Clickable
|
showRemoteLifecycle widget.Clickable
|
||||||
showSyncRemote widget.Clickable
|
showSyncLocal widget.Clickable
|
||||||
showSyncPull widget.Clickable
|
showSyncRemote widget.Clickable
|
||||||
showSyncPush widget.Clickable
|
showSyncPull widget.Clickable
|
||||||
allowApproval widget.Clickable
|
showSyncPush widget.Clickable
|
||||||
denyApproval widget.Clickable
|
allowApproval widget.Clickable
|
||||||
cancelApproval widget.Clickable
|
denyApproval widget.Clickable
|
||||||
approvalPermanent widget.Bool
|
cancelApproval widget.Clickable
|
||||||
rememberRemoteAuth widget.Bool
|
approvalPermanent widget.Bool
|
||||||
apiPolicyAllow widget.Bool
|
rememberRemoteAuth widget.Bool
|
||||||
apiPolicyGroupScopeW widget.Bool
|
apiPolicyAllow widget.Bool
|
||||||
apiTokenDisabled widget.Bool
|
apiPolicyGroupScopeW widget.Bool
|
||||||
entryClicks []widget.Clickable
|
apiTokenDisabled widget.Bool
|
||||||
apiTokenClicks []widget.Clickable
|
entryClicks []widget.Clickable
|
||||||
apiPolicyRemoves []widget.Clickable
|
apiTokenClicks []widget.Clickable
|
||||||
apiAuditClicks []widget.Clickable
|
apiPolicyRemoves []widget.Clickable
|
||||||
historyClicks []widget.Clickable
|
apiAuditClicks []widget.Clickable
|
||||||
attachmentClicks []widget.Clickable
|
historyClicks []widget.Clickable
|
||||||
breadcrumbs []widget.Clickable
|
attachmentClicks []widget.Clickable
|
||||||
groupClicks []widget.Clickable
|
breadcrumbs []widget.Clickable
|
||||||
recentVaultClicks []widget.Clickable
|
groupClicks []widget.Clickable
|
||||||
recentRemoteClicks []widget.Clickable
|
recentVaultClicks []widget.Clickable
|
||||||
removeCustomFields []widget.Clickable
|
recentRemoteClicks []widget.Clickable
|
||||||
state appstate.State
|
removeCustomFields []widget.Clickable
|
||||||
visible []entry
|
state appstate.State
|
||||||
currentPath []string
|
visible []entry
|
||||||
syncedPath []string
|
currentPath []string
|
||||||
selectedHistoryIndex int
|
syncedPath []string
|
||||||
showPassword bool
|
selectedHistoryIndex int
|
||||||
togglePassword widget.Clickable
|
showPassword bool
|
||||||
copyAPITokenSecret widget.Clickable
|
togglePassword widget.Clickable
|
||||||
issueAPIToken widget.Clickable
|
copyAPITokenSecret widget.Clickable
|
||||||
saveAPIToken widget.Clickable
|
issueAPIToken widget.Clickable
|
||||||
rotateAPIToken widget.Clickable
|
saveAPIToken widget.Clickable
|
||||||
disableAPIToken widget.Clickable
|
rotateAPIToken widget.Clickable
|
||||||
revokeAPIToken widget.Clickable
|
disableAPIToken widget.Clickable
|
||||||
deleteAPIToken widget.Clickable
|
revokeAPIToken widget.Clickable
|
||||||
addAPIPolicyRule widget.Clickable
|
deleteAPIToken widget.Clickable
|
||||||
phoneSplit widget.Float
|
addAPIPolicyRule widget.Clickable
|
||||||
splitDrag gesture.Drag
|
phoneSplit widget.Float
|
||||||
splitBase float32
|
splitDrag gesture.Drag
|
||||||
splitStartY float32
|
splitBase float32
|
||||||
phoneSpan int
|
splitStartY float32
|
||||||
eyeIcon *widget.Icon
|
phoneSpan int
|
||||||
eyeOffIcon *widget.Icon
|
eyeIcon *widget.Icon
|
||||||
copyIcon *widget.Icon
|
eyeOffIcon *widget.Icon
|
||||||
expandMoreIcon *widget.Icon
|
copyIcon *widget.Icon
|
||||||
expandLessIcon *widget.Icon
|
expandMoreIcon *widget.Icon
|
||||||
chevronDownIcon *widget.Icon
|
expandLessIcon *widget.Icon
|
||||||
clipboardWriter clipboard.Writer
|
chevronDownIcon *widget.Icon
|
||||||
loadingMessage string
|
clipboardWriter clipboard.Writer
|
||||||
lifecycleMode string
|
loadingMessage string
|
||||||
syncSourceMode syncSourceMode
|
lifecycleMode string
|
||||||
syncDirection syncDirection
|
syncSourceMode syncSourceMode
|
||||||
syncLocalPath widget.Editor
|
syncDirection syncDirection
|
||||||
syncRemoteBaseURL widget.Editor
|
syncLocalPath widget.Editor
|
||||||
syncRemotePath widget.Editor
|
syncRemoteBaseURL widget.Editor
|
||||||
syncRemoteUsername widget.Editor
|
syncRemotePath widget.Editor
|
||||||
syncRemotePassword widget.Editor
|
syncRemoteUsername widget.Editor
|
||||||
syncDialogOpen bool
|
syncRemotePassword widget.Editor
|
||||||
syncMenuOpen bool
|
syncDialogOpen bool
|
||||||
securityDialogOpen bool
|
syncMenuOpen bool
|
||||||
showSyncPassword bool
|
securityDialogOpen bool
|
||||||
keyboardFocus focusID
|
showSyncPassword bool
|
||||||
defaultSaveAsPath string
|
keyboardFocus focusID
|
||||||
recentVaultsPath string
|
defaultSaveAsPath string
|
||||||
uiPreferencesPath string
|
recentVaultsPath string
|
||||||
recentRemotesPath string
|
uiPreferencesPath string
|
||||||
autofillCachePath string
|
recentRemotesPath string
|
||||||
editingEntry bool
|
autofillCachePath string
|
||||||
groupControlsHidden bool
|
editingEntry bool
|
||||||
recentVaults []string
|
groupControlsHidden bool
|
||||||
recentRemotes []recentRemoteRecord
|
lifecycleAdvancedHidden bool
|
||||||
recentVaultGroups map[string][]string
|
recentVaults []string
|
||||||
recentVaultUsedAt map[string]time.Time
|
recentRemotes []recentRemoteRecord
|
||||||
entriesState entriesSectionState
|
recentVaultGroups map[string][]string
|
||||||
deleteGroupPath []string
|
recentVaultUsedAt map[string]time.Time
|
||||||
apiPolicyGroupScope bool
|
entriesState entriesSectionState
|
||||||
apiTokenSecret string
|
deleteGroupPath []string
|
||||||
selectedAuditIndex int
|
apiPolicyGroupScope bool
|
||||||
statusExpiresAt time.Time
|
apiTokenSecret string
|
||||||
now func() time.Time
|
selectedAuditIndex int
|
||||||
apiHost *api.Host
|
statusExpiresAt time.Time
|
||||||
auditLog *apiaudit.Log
|
now func() time.Time
|
||||||
grpcAddress string
|
apiHost *api.Host
|
||||||
|
auditLog *apiaudit.Log
|
||||||
|
grpcAddress string
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -407,21 +410,22 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths)
|
|||||||
lifecycleList: widget.List{
|
lifecycleList: widget.List{
|
||||||
List: layout.List{Axis: layout.Vertical},
|
List: layout.List{Axis: layout.Vertical},
|
||||||
},
|
},
|
||||||
state: appstate.State{},
|
state: appstate.State{},
|
||||||
selectedHistoryIndex: -1,
|
selectedHistoryIndex: -1,
|
||||||
selectedAuditIndex: -1,
|
selectedAuditIndex: -1,
|
||||||
lifecycleMode: "local",
|
lifecycleMode: "local",
|
||||||
defaultSaveAsPath: paths.DefaultSaveAsPath,
|
defaultSaveAsPath: paths.DefaultSaveAsPath,
|
||||||
recentVaultsPath: paths.RecentVaultsPath,
|
recentVaultsPath: paths.RecentVaultsPath,
|
||||||
uiPreferencesPath: paths.UIPreferencesPath,
|
uiPreferencesPath: paths.UIPreferencesPath,
|
||||||
recentRemotesPath: paths.RecentRemotesPath,
|
recentRemotesPath: paths.RecentRemotesPath,
|
||||||
autofillCachePath: paths.AutofillCachePath,
|
autofillCachePath: paths.AutofillCachePath,
|
||||||
recentVaultGroups: map[string][]string{},
|
recentVaultGroups: map[string][]string{},
|
||||||
recentVaultUsedAt: map[string]time.Time{},
|
recentVaultUsedAt: map[string]time.Time{},
|
||||||
now: time.Now,
|
lifecycleAdvancedHidden: true,
|
||||||
syncSourceMode: syncSourceLocal,
|
now: time.Now,
|
||||||
syncDirection: syncDirectionPull,
|
syncSourceMode: syncSourceLocal,
|
||||||
apiPolicyGroupScope: true,
|
syncDirection: syncDirectionPull,
|
||||||
|
apiPolicyGroupScope: true,
|
||||||
}
|
}
|
||||||
u.apiPolicyAllow.Value = true
|
u.apiPolicyAllow.Value = true
|
||||||
u.apiPolicyGroupScopeW.Value = true
|
u.apiPolicyGroupScopeW.Value = true
|
||||||
@@ -1098,6 +1102,7 @@ func (u *ui) loadUIPreferences() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
u.groupControlsHidden = prefs.GroupControlsHidden
|
u.groupControlsHidden = prefs.GroupControlsHidden
|
||||||
|
u.lifecycleAdvancedHidden = prefs.LifecycleAdvancedHidden
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) saveUIPreferences() {
|
func (u *ui) saveUIPreferences() {
|
||||||
@@ -1108,7 +1113,8 @@ func (u *ui) saveUIPreferences() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
content, err := json.MarshalIndent(uiPreferences{
|
content, err := json.MarshalIndent(uiPreferences{
|
||||||
GroupControlsHidden: u.groupControlsHidden,
|
GroupControlsHidden: u.groupControlsHidden,
|
||||||
|
LifecycleAdvancedHidden: u.lifecycleAdvancedHidden,
|
||||||
}, "", " ")
|
}, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -1761,6 +1767,10 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
|
|||||||
for u.showRemoteLifecycle.Clicked(gtx) {
|
for u.showRemoteLifecycle.Clicked(gtx) {
|
||||||
u.lifecycleMode = "remote"
|
u.lifecycleMode = "remote"
|
||||||
}
|
}
|
||||||
|
for u.toggleLifecycleAdvanced.Clicked(gtx) {
|
||||||
|
u.lifecycleAdvancedHidden = !u.lifecycleAdvancedHidden
|
||||||
|
u.saveUIPreferences()
|
||||||
|
}
|
||||||
for u.showSyncLocal.Clicked(gtx) {
|
for u.showSyncLocal.Clicked(gtx) {
|
||||||
u.syncSourceMode = syncSourceLocal
|
u.syncSourceMode = syncSourceLocal
|
||||||
}
|
}
|
||||||
@@ -2596,39 +2606,19 @@ 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 {
|
||||||
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 {
|
||||||
btn := material.Button(u.theme, &u.showEntries, "Entries")
|
return sectionTabButton(gtx, u.theme, &u.showEntries, "Entries", u.state.Section == appstate.SectionEntries)
|
||||||
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(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
btn := material.Button(u.theme, &u.showRecycle, "Recycle Bin")
|
return sectionTabButton(gtx, u.theme, &u.showRecycle, "Recycle Bin", u.state.Section == appstate.SectionRecycleBin)
|
||||||
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(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
btn := material.Button(u.theme, &u.showAPITokens, "API Tokens")
|
return sectionTabButton(gtx, u.theme, &u.showAPITokens, "API Tokens", u.state.Section == appstate.SectionAPITokens)
|
||||||
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(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
btn := material.Button(u.theme, &u.showAPIAudit, "API Audit")
|
return sectionTabButton(gtx, u.theme, &u.showAPIAudit, "API Audit", u.state.Section == appstate.SectionAPIAudit)
|
||||||
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)
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -3345,6 +3335,21 @@ func tonedButton(gtx layout.Context, th *material.Theme, click *widget.Clickable
|
|||||||
return btn.Layout(gtx)
|
return btn.Layout(gtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sectionTabButton(gtx layout.Context, th *material.Theme, click *widget.Clickable, label string, active bool) layout.Dimensions {
|
||||||
|
btn := material.Button(th, click, label)
|
||||||
|
btn.CornerRadius = unit.Dp(10)
|
||||||
|
btn.TextSize = unit.Sp(11)
|
||||||
|
btn.Inset = layout.Inset{Top: 5, Bottom: 5, Left: 9, Right: 9}
|
||||||
|
if active {
|
||||||
|
btn.Background = accentColor
|
||||||
|
btn.Color = color.NRGBA{R: 255, G: 252, B: 247, A: 255}
|
||||||
|
} else {
|
||||||
|
btn.Background = selectedColor
|
||||||
|
btn.Color = accentColor
|
||||||
|
}
|
||||||
|
return btn.Layout(gtx)
|
||||||
|
}
|
||||||
|
|
||||||
func fill(c color.NRGBA) layout.Widget {
|
func fill(c color.NRGBA) layout.Widget {
|
||||||
return func(gtx layout.Context) layout.Dimensions {
|
return func(gtx layout.Context) layout.Dimensions {
|
||||||
paint.FillShape(gtx.Ops, c, clip.Rect{Max: gtx.Constraints.Min}.Op())
|
paint.FillShape(gtx.Ops, c, clip.Rect{Max: gtx.Constraints.Min}.Op())
|
||||||
|
|||||||
+78
-5
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
@@ -17,11 +18,11 @@ 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 {
|
||||||
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 tonedButton(gtx, u.theme, &u.showLocalLifecycle, "Local Vault")
|
return sectionTabButton(gtx, u.theme, &u.showLocalLifecycle, "Local Vault", u.lifecycleMode == "local")
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Width: 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.showRemoteLifecycle, "Remote Vault")
|
return sectionTabButton(gtx, u.theme, &u.showRemoteLifecycle, "Remote Vault", u.lifecycleMode == "remote")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
@@ -61,9 +62,18 @@ func (u *ui) lifecycleControls(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(labeledEditorHelp(u.theme, "Cipher", "Supported values: aes256, chacha20", &u.securityCipher, false)),
|
layout.Rigid(u.lifecycleAdvancedDisclosure),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(labeledEditorHelp(u.theme, "KDF", "Supported values: aes-kdf, argon2", &u.securityKDF, false)),
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
if u.lifecycleAdvancedHidden {
|
||||||
|
return layout.Dimensions{}
|
||||||
|
}
|
||||||
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(labeledEditorHelp(u.theme, "Cipher", "Used for new vaults and future saves. Supported values: aes256, chacha20.", &u.securityCipher, false)),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(labeledEditorHelp(u.theme, "KDF", "Used for new vaults and future saves. Supported values: aes-kdf, argon2.", &u.securityKDF, false)),
|
||||||
|
)
|
||||||
|
}),
|
||||||
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 {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
if u.lifecycleMode == "remote" {
|
if u.lifecycleMode == "remote" {
|
||||||
@@ -82,6 +92,36 @@ func (u *ui) lifecycleControls(gtx layout.Context) layout.Dimensions {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ui) lifecycleAdvancedDisclosure(gtx layout.Context) layout.Dimensions {
|
||||||
|
return u.toggleLifecycleAdvanced.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.UniformInset(unit.Dp(2)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
icon := u.expandLessIcon
|
||||||
|
if u.lifecycleAdvancedHidden {
|
||||||
|
icon = u.expandMoreIcon
|
||||||
|
}
|
||||||
|
if icon != nil {
|
||||||
|
return icon.Layout(gtx, accentColor)
|
||||||
|
}
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(16), ">")
|
||||||
|
if !u.lifecycleAdvancedHidden {
|
||||||
|
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(12), "Advanced Vault Settings")
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ui) recentVaultList(gtx layout.Context) layout.Dimensions {
|
func (u *ui) recentVaultList(gtx layout.Context) layout.Dimensions {
|
||||||
if len(u.recentVaults) == 0 {
|
if len(u.recentVaults) == 0 {
|
||||||
return layout.Dimensions{}
|
return layout.Dimensions{}
|
||||||
@@ -102,6 +142,9 @@ func (u *ui) recentVaultList(gtx layout.Context) layout.Dimensions {
|
|||||||
for i, path := range u.recentVaults {
|
for i, path := range u.recentVaults {
|
||||||
index := i
|
index := i
|
||||||
label := path
|
label := path
|
||||||
|
if friendly := friendlyRecentVaultLabel(path); 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 tonedButton(gtx, u.theme, &u.recentVaultClicks[index], label)
|
||||||
}))
|
}))
|
||||||
@@ -134,7 +177,7 @@ func (u *ui) recentRemoteList(gtx layout.Context) layout.Dimensions {
|
|||||||
children := make([]layout.FlexChild, 0, len(u.recentRemotes)*2)
|
children := make([]layout.FlexChild, 0, len(u.recentRemotes)*2)
|
||||||
for i, record := range u.recentRemotes {
|
for i, record := range u.recentRemotes {
|
||||||
index := i
|
index := i
|
||||||
label := record.BaseURL + " / " + record.Path
|
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 tonedButton(gtx, u.theme, &u.recentRemoteClicks[index], label)
|
||||||
}))
|
}))
|
||||||
@@ -148,6 +191,36 @@ func (u *ui) recentRemoteList(gtx layout.Context) layout.Dimensions {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func friendlyRecentVaultLabel(path string) string {
|
||||||
|
value := strings.TrimSpace(path)
|
||||||
|
if value == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
base := filepath.Base(value)
|
||||||
|
if base == "." || base == string(filepath.Separator) || base == "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
func friendlyRecentRemoteLabel(record recentRemoteRecord) string {
|
||||||
|
baseURL := strings.TrimSpace(record.BaseURL)
|
||||||
|
path := strings.TrimSpace(record.Path)
|
||||||
|
if baseURL == "" && path == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
host := strings.TrimSpace(strings.TrimPrefix(strings.TrimPrefix(baseURL, "https://"), "http://"))
|
||||||
|
host = strings.TrimSuffix(host, "/")
|
||||||
|
switch {
|
||||||
|
case host == "":
|
||||||
|
return path
|
||||||
|
case path == "":
|
||||||
|
return host
|
||||||
|
default:
|
||||||
|
return host + " · " + path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user