Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cdf0c0c2c7 | |||
| 6ccff23804 | |||
| c3a9c0fddb |
@@ -0,0 +1,95 @@
|
|||||||
|
package actions
|
||||||
|
|
||||||
|
import "git.julianfamily.org/keepassgo/internal/appstate"
|
||||||
|
|
||||||
|
type SyncMenuModel struct {
|
||||||
|
HasOpenVault bool
|
||||||
|
HasSelectedBinding bool
|
||||||
|
ShowSelectors bool
|
||||||
|
ShowShare bool
|
||||||
|
ShowSaveCurrentBinding bool
|
||||||
|
SavedBindingSummary SyncMenuBindingSummary
|
||||||
|
RemoteBaseURL string
|
||||||
|
RemotePath string
|
||||||
|
RemoteUsername string
|
||||||
|
RemotePassword string
|
||||||
|
SelectedVaultSyncMode appstate.SyncMode
|
||||||
|
}
|
||||||
|
|
||||||
|
type SyncMenuBindingSummary struct {
|
||||||
|
ProfileLabel string
|
||||||
|
CredentialLabel string
|
||||||
|
SyncLabel string
|
||||||
|
OK bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m SyncMenuModel) SavedBindingHeading() string {
|
||||||
|
if !m.ShowSelectors {
|
||||||
|
return "Use this vault's saved remote sync target"
|
||||||
|
}
|
||||||
|
return "Use a saved remote profile from this vault"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m SyncMenuModel) OpenSelectedButtonLabel() string {
|
||||||
|
if !m.ShowSelectors {
|
||||||
|
return "Use Remote Sync"
|
||||||
|
}
|
||||||
|
return "Open Saved Remote"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m SyncMenuModel) ShowDirectRemoteSyncShortcut() bool {
|
||||||
|
return m.HasOpenVault && m.HasSelectedBinding
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m SyncMenuModel) DirectRemoteSyncShortcutLabel() string {
|
||||||
|
return "Use Remote Sync"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m SyncMenuModel) ShowRemoteSyncSettingsShortcut() bool {
|
||||||
|
return m.HasOpenVault && m.HasSelectedBinding
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m SyncMenuModel) RemoteSyncSettingsShortcutLabel() string {
|
||||||
|
return "Remote Sync Settings"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m SyncMenuModel) ShowRemoveRemoteSyncShortcut() bool {
|
||||||
|
return m.ShowRemoteSyncSettingsShortcut()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m SyncMenuModel) RemoveRemoteSyncShortcutLabel() string {
|
||||||
|
return "Stop Using Remote Sync"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m SyncMenuModel) ShowRemoteSyncSetupShortcut() bool {
|
||||||
|
return m.HasOpenVault && !m.HasSelectedBinding
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m SyncMenuModel) RemoteSyncSetupShortcutLabel() string {
|
||||||
|
return "Set Up Remote Sync"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m SyncMenuModel) ActionLabels() []string {
|
||||||
|
labels := []string{"Open Advanced Sync"}
|
||||||
|
if m.ShowRemoteSyncSetupShortcut() {
|
||||||
|
labels = append(labels, m.RemoteSyncSetupShortcutLabel())
|
||||||
|
}
|
||||||
|
if m.ShowDirectRemoteSyncShortcut() {
|
||||||
|
labels = append(labels, m.DirectRemoteSyncShortcutLabel())
|
||||||
|
}
|
||||||
|
if m.ShowRemoteSyncSettingsShortcut() {
|
||||||
|
labels = append(labels, m.RemoteSyncSettingsShortcutLabel())
|
||||||
|
}
|
||||||
|
if m.ShowRemoveRemoteSyncShortcut() {
|
||||||
|
labels = append(labels, m.RemoveRemoteSyncShortcutLabel())
|
||||||
|
}
|
||||||
|
return labels
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m SyncMenuModel) SaveCurrentRemoteBindingHeading() string {
|
||||||
|
return "Bind this local vault to the current remote target"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m SyncMenuModel) SaveCurrentRemoteBindingButtonLabel() string {
|
||||||
|
return "Save Remote In Vault"
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
//go:build !android
|
|
||||||
|
|
||||||
package appui
|
|
||||||
|
|
||||||
func newPlatformVaultSharer(goos string) vaultSharer {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
+19
-22
@@ -36,6 +36,7 @@ import (
|
|||||||
"git.julianfamily.org/keepassgo/internal/apiaudit"
|
"git.julianfamily.org/keepassgo/internal/apiaudit"
|
||||||
"git.julianfamily.org/keepassgo/internal/apitokens"
|
"git.julianfamily.org/keepassgo/internal/apitokens"
|
||||||
"git.julianfamily.org/keepassgo/internal/appstate"
|
"git.julianfamily.org/keepassgo/internal/appstate"
|
||||||
|
"git.julianfamily.org/keepassgo/internal/appui/platform"
|
||||||
keepassassets "git.julianfamily.org/keepassgo/internal/assets"
|
keepassassets "git.julianfamily.org/keepassgo/internal/assets"
|
||||||
"git.julianfamily.org/keepassgo/internal/autofillcache"
|
"git.julianfamily.org/keepassgo/internal/autofillcache"
|
||||||
"git.julianfamily.org/keepassgo/internal/clipboard"
|
"git.julianfamily.org/keepassgo/internal/clipboard"
|
||||||
@@ -466,7 +467,7 @@ type ui struct {
|
|||||||
settingsIcon *widget.Icon
|
settingsIcon *widget.Icon
|
||||||
menuIcon *widget.Icon
|
menuIcon *widget.Icon
|
||||||
clipboardWriter clipboard.Writer
|
clipboardWriter clipboard.Writer
|
||||||
vaultSharer vaultSharer
|
vaultSharer platform.VaultSharer
|
||||||
loadingMessage string
|
loadingMessage string
|
||||||
loadingActionLabel string
|
loadingActionLabel string
|
||||||
lifecycleMode string
|
lifecycleMode string
|
||||||
@@ -546,10 +547,6 @@ type backgroundActionResult struct {
|
|||||||
id int
|
id int
|
||||||
}
|
}
|
||||||
|
|
||||||
type vaultSharer interface {
|
|
||||||
ShareVault(path, title string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
bgColor = color.NRGBA{R: 242, G: 239, B: 233, A: 255}
|
bgColor = color.NRGBA{R: 242, G: 239, B: 233, A: 255}
|
||||||
panelColor = color.NRGBA{R: 250, G: 248, B: 244, A: 255}
|
panelColor = color.NRGBA{R: 250, G: 248, B: 244, A: 255}
|
||||||
@@ -691,7 +688,7 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths)
|
|||||||
syncDefaultDirection: syncDirectionPull,
|
syncDefaultDirection: syncDirectionPull,
|
||||||
apiPolicyGroupScope: true,
|
apiPolicyGroupScope: true,
|
||||||
autofillNoticePreference: autofillNoticeAll,
|
autofillNoticePreference: autofillNoticeAll,
|
||||||
vaultSharer: newPlatformVaultSharer(runtime.GOOS),
|
vaultSharer: platform.NewVaultSharer(runtime.GOOS),
|
||||||
backgroundResults: make(chan backgroundActionResult, 8),
|
backgroundResults: make(chan backgroundActionResult, 8),
|
||||||
phoneGroupBrowserExpanded: true,
|
phoneGroupBrowserExpanded: true,
|
||||||
}
|
}
|
||||||
@@ -2743,60 +2740,60 @@ func (u *ui) shouldShowSavedRemoteBindingSelectors() bool {
|
|||||||
|
|
||||||
func (u *ui) savedRemoteBindingSummary() (profileLabel, credentialLabel, syncLabel string, ok bool) {
|
func (u *ui) savedRemoteBindingSummary() (profileLabel, credentialLabel, syncLabel string, ok bool) {
|
||||||
summary := u.computeSavedRemoteBindingSummary()
|
summary := u.computeSavedRemoteBindingSummary()
|
||||||
return summary.profileLabel, summary.credentialLabel, summary.syncLabel, summary.ok
|
return summary.ProfileLabel, summary.CredentialLabel, summary.SyncLabel, summary.OK
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) savedRemoteBindingHeading() string {
|
func (u *ui) savedRemoteBindingHeading() string {
|
||||||
return u.buildSyncMenuModel().savedBindingHeading()
|
return u.buildSyncMenuModel().SavedBindingHeading()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) openSelectedVaultRemoteButtonLabel() string {
|
func (u *ui) openSelectedVaultRemoteButtonLabel() string {
|
||||||
return u.buildSyncMenuModel().openSelectedButtonLabel()
|
return u.buildSyncMenuModel().OpenSelectedButtonLabel()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) shouldShowDirectRemoteSyncShortcut() bool {
|
func (u *ui) shouldShowDirectRemoteSyncShortcut() bool {
|
||||||
if !u.hasOpenVault() || u.isVaultLocked() || u.state.Section != appstate.SectionEntries {
|
if !u.hasOpenVault() || u.isVaultLocked() || u.state.Section != appstate.SectionEntries {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return u.buildSyncMenuModel().showDirectRemoteSyncShortcut()
|
return u.buildSyncMenuModel().ShowDirectRemoteSyncShortcut()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) directRemoteSyncShortcutLabel() string {
|
func (u *ui) directRemoteSyncShortcutLabel() string {
|
||||||
return u.buildSyncMenuModel().directRemoteSyncShortcutLabel()
|
return u.buildSyncMenuModel().DirectRemoteSyncShortcutLabel()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) shouldShowRemoteSyncSettingsShortcut() bool {
|
func (u *ui) shouldShowRemoteSyncSettingsShortcut() bool {
|
||||||
if !u.hasOpenVault() || u.isVaultLocked() || u.state.Section != appstate.SectionEntries {
|
if !u.hasOpenVault() || u.isVaultLocked() || u.state.Section != appstate.SectionEntries {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return u.buildSyncMenuModel().showRemoteSyncSettingsShortcut()
|
return u.buildSyncMenuModel().ShowRemoteSyncSettingsShortcut()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) remoteSyncSettingsShortcutLabel() string {
|
func (u *ui) remoteSyncSettingsShortcutLabel() string {
|
||||||
return u.buildSyncMenuModel().remoteSyncSettingsShortcutLabel()
|
return u.buildSyncMenuModel().RemoteSyncSettingsShortcutLabel()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) shouldShowRemoveRemoteSyncShortcut() bool {
|
func (u *ui) shouldShowRemoveRemoteSyncShortcut() bool {
|
||||||
return u.buildSyncMenuModel().showRemoveRemoteSyncShortcut()
|
return u.buildSyncMenuModel().ShowRemoveRemoteSyncShortcut()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) removeRemoteSyncShortcutLabel() string {
|
func (u *ui) removeRemoteSyncShortcutLabel() string {
|
||||||
return u.buildSyncMenuModel().removeRemoteSyncShortcutLabel()
|
return u.buildSyncMenuModel().RemoveRemoteSyncShortcutLabel()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) shouldShowRemoteSyncSetupShortcut() bool {
|
func (u *ui) shouldShowRemoteSyncSetupShortcut() bool {
|
||||||
if !u.hasOpenVault() || u.isVaultLocked() || u.state.Section != appstate.SectionEntries {
|
if !u.hasOpenVault() || u.isVaultLocked() || u.state.Section != appstate.SectionEntries {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return u.buildSyncMenuModel().showRemoteSyncSetupShortcut()
|
return u.buildSyncMenuModel().ShowRemoteSyncSetupShortcut()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) remoteSyncSetupShortcutLabel() string {
|
func (u *ui) remoteSyncSetupShortcutLabel() string {
|
||||||
return u.buildSyncMenuModel().remoteSyncSetupShortcutLabel()
|
return u.buildSyncMenuModel().RemoteSyncSetupShortcutLabel()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) syncMenuActionLabels() []string {
|
func (u *ui) syncMenuActionLabels() []string {
|
||||||
return u.buildSyncMenuModel().actionLabels()
|
return u.buildSyncMenuModel().ActionLabels()
|
||||||
}
|
}
|
||||||
|
|
||||||
func remoteBindingSuffix(baseURL, path, username string) string {
|
func remoteBindingSuffix(baseURL, path, username string) string {
|
||||||
@@ -2904,11 +2901,11 @@ func (u *ui) removeSelectedRemoteBindingAction() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) saveCurrentRemoteBindingHeading() string {
|
func (u *ui) saveCurrentRemoteBindingHeading() string {
|
||||||
return u.buildSyncMenuModel().saveCurrentRemoteBindingHeading()
|
return u.buildSyncMenuModel().SaveCurrentRemoteBindingHeading()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) saveCurrentRemoteBindingButtonLabel() string {
|
func (u *ui) saveCurrentRemoteBindingButtonLabel() string {
|
||||||
return u.buildSyncMenuModel().saveCurrentRemoteBindingButtonLabel()
|
return u.buildSyncMenuModel().SaveCurrentRemoteBindingButtonLabel()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) materializeCurrentRemoteCache() error {
|
func (u *ui) materializeCurrentRemoteCache() error {
|
||||||
@@ -6980,7 +6977,7 @@ func run(w *app.Window, mode string, paths statePaths, grpcAddr string) error {
|
|||||||
ui := newUIWithSession(mode, manager, paths)
|
ui := newUIWithSession(mode, manager, paths)
|
||||||
ui.fileExplorer = explorer.NewExplorer(w)
|
ui.fileExplorer = explorer.NewExplorer(w)
|
||||||
ui.invalidate = w.Invalidate
|
ui.invalidate = w.Invalidate
|
||||||
ui.clipboardWriter = newPlatformClipboardWriter(runtime.GOOS, w.Invalidate)
|
ui.clipboardWriter = platform.NewClipboardWriter(runtime.GOOS, w.Invalidate)
|
||||||
host, err := api.StartHost(grpcAddr, manager, passwords.DefaultProfiles(), ui.clipboardWriter, func() bool { return ui.state.Dirty })
|
host, err := api.StartHost(grpcAddr, manager, passwords.DefaultProfiles(), ui.clipboardWriter, func() bool { return ui.state.Dirty })
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.state.ErrorMessage = fmt.Sprintf("start gRPC API: %v", err)
|
ui.state.ErrorMessage = fmt.Sprintf("start gRPC API: %v", err)
|
||||||
@@ -7001,7 +6998,7 @@ func run(w *app.Window, mode string, paths statePaths, grpcAddr string) error {
|
|||||||
gtx := app.NewContext(&ops, e)
|
gtx := app.NewContext(&ops, e)
|
||||||
ui.processBackgroundActions()
|
ui.processBackgroundActions()
|
||||||
ui.layout(gtx)
|
ui.layout(gtx)
|
||||||
processClipboardWrites(gtx, ui.clipboardWriter)
|
platform.ProcessClipboardWrites(gtx, ui.clipboardWriter)
|
||||||
e.Frame(gtx.Ops)
|
e.Frame(gtx.Ops)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package appui
|
package layout
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
@@ -8,47 +8,62 @@ import (
|
|||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
type dropdownAnchor struct {
|
func AnchoredMenuX(triggerWidth, menuWidth int) int {
|
||||||
|
return triggerWidth - menuWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
func AnchoredMenuOriginX(containerWidth, rowOriginX, triggerRightX, menuWidth int) int {
|
||||||
|
x := rowOriginX + triggerRightX - menuWidth
|
||||||
|
if x < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if x+menuWidth > containerWidth {
|
||||||
|
return max(0, containerWidth-menuWidth)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
type DropdownAnchor struct {
|
||||||
TriggerRightX int
|
TriggerRightX int
|
||||||
TriggerBottomY int
|
TriggerBottomY int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a dropdownAnchor) point() image.Point {
|
func (a DropdownAnchor) Point() image.Point {
|
||||||
return image.Pt(a.TriggerRightX, a.TriggerBottomY)
|
return image.Pt(a.TriggerRightX, a.TriggerBottomY)
|
||||||
}
|
}
|
||||||
|
|
||||||
type dropdownSurface struct {
|
type DropdownSurface struct {
|
||||||
ContainerWidth int
|
ContainerWidth int
|
||||||
LeftInset int
|
LeftInset int
|
||||||
TopInset int
|
TopInset int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s dropdownSurface) menuConstraints(gtx layout.Context) layout.Context {
|
func (s DropdownSurface) MenuConstraints(gtx layout.Context) layout.Context {
|
||||||
menuGTX := gtx
|
menuGTX := gtx
|
||||||
menuGTX.Constraints.Min = image.Point{}
|
menuGTX.Constraints.Min = image.Point{}
|
||||||
menuGTX.Constraints.Max.X = max(0, s.ContainerWidth)
|
menuGTX.Constraints.Max.X = max(0, s.ContainerWidth)
|
||||||
return menuGTX
|
return menuGTX
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s dropdownSurface) origin(anchor dropdownAnchor, menuWidth int) image.Point {
|
func (s DropdownSurface) Origin(anchor DropdownAnchor, menuWidth int) image.Point {
|
||||||
x := s.LeftInset + anchoredMenuOriginX(s.ContainerWidth, 0, anchor.TriggerRightX, menuWidth)
|
x := s.LeftInset + AnchoredMenuOriginX(s.ContainerWidth, 0, anchor.TriggerRightX, menuWidth)
|
||||||
y := s.TopInset + anchor.TriggerBottomY
|
y := s.TopInset + anchor.TriggerBottomY
|
||||||
return image.Pt(x, y)
|
return image.Pt(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s dropdownSurface) draw(gtx layout.Context, anchor dropdownAnchor, menu layout.Widget) layout.Dimensions {
|
func (s DropdownSurface) Draw(gtx layout.Context, anchor DropdownAnchor, menu layout.Widget) layout.Dimensions {
|
||||||
menuGTX := s.menuConstraints(gtx)
|
menuGTX := s.MenuConstraints(gtx)
|
||||||
menuOps := op.Record(gtx.Ops)
|
menuOps := op.Record(gtx.Ops)
|
||||||
menuDims := layout.Inset{Top: unit.Dp(6)}.Layout(menuGTX, menu)
|
menuDims := layout.Inset{Top: unit.Dp(6)}.Layout(menuGTX, menu)
|
||||||
menuCall := menuOps.Stop()
|
menuCall := menuOps.Stop()
|
||||||
menuOrigin := s.origin(anchor, menuDims.Size.X)
|
menuOrigin := s.Origin(anchor, menuDims.Size.X)
|
||||||
stack := op.Offset(menuOrigin).Push(gtx.Ops)
|
stack := op.Offset(menuOrigin).Push(gtx.Ops)
|
||||||
menuCall.Add(gtx.Ops)
|
menuCall.Add(gtx.Ops)
|
||||||
stack.Pop()
|
stack.Pop()
|
||||||
return layout.Dimensions{Size: gtx.Constraints.Max}
|
return layout.Dimensions{Size: gtx.Constraints.Max}
|
||||||
}
|
}
|
||||||
|
|
||||||
type headerActionMetrics struct {
|
type HeaderActionMetrics struct {
|
||||||
RowOriginX int
|
RowOriginX int
|
||||||
Spacing int
|
Spacing int
|
||||||
RowDims layout.Dimensions
|
RowDims layout.Dimensions
|
||||||
@@ -57,16 +72,16 @@ type headerActionMetrics struct {
|
|||||||
MainDims layout.Dimensions
|
MainDims layout.Dimensions
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m headerActionMetrics) syncAnchor() dropdownAnchor {
|
func (m HeaderActionMetrics) SyncAnchor() DropdownAnchor {
|
||||||
return dropdownAnchor{
|
return DropdownAnchor{
|
||||||
TriggerRightX: m.RowOriginX + m.SyncDims.Size.X,
|
TriggerRightX: m.RowOriginX + m.SyncDims.Size.X,
|
||||||
TriggerBottomY: m.RowDims.Size.Y,
|
TriggerBottomY: m.RowDims.Size.Y,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m headerActionMetrics) mainAnchor() dropdownAnchor {
|
func (m HeaderActionMetrics) MainAnchor() DropdownAnchor {
|
||||||
triggerRightX := m.SyncDims.Size.X + m.Spacing + m.LockDims.Size.X + m.Spacing + m.MainDims.Size.X
|
triggerRightX := m.SyncDims.Size.X + m.Spacing + m.LockDims.Size.X + m.Spacing + m.MainDims.Size.X
|
||||||
return dropdownAnchor{
|
return DropdownAnchor{
|
||||||
TriggerRightX: m.RowOriginX + triggerRightX,
|
TriggerRightX: m.RowOriginX + triggerRightX,
|
||||||
TriggerBottomY: m.RowDims.Size.Y,
|
TriggerBottomY: m.RowDims.Size.Y,
|
||||||
}
|
}
|
||||||
+44
-43
@@ -25,6 +25,7 @@ import (
|
|||||||
"git.julianfamily.org/keepassgo/internal/apiaudit"
|
"git.julianfamily.org/keepassgo/internal/apiaudit"
|
||||||
"git.julianfamily.org/keepassgo/internal/apitokens"
|
"git.julianfamily.org/keepassgo/internal/apitokens"
|
||||||
"git.julianfamily.org/keepassgo/internal/appstate"
|
"git.julianfamily.org/keepassgo/internal/appstate"
|
||||||
|
appuilayout "git.julianfamily.org/keepassgo/internal/appui/layout"
|
||||||
"git.julianfamily.org/keepassgo/internal/clipboard"
|
"git.julianfamily.org/keepassgo/internal/clipboard"
|
||||||
"git.julianfamily.org/keepassgo/internal/passwords"
|
"git.julianfamily.org/keepassgo/internal/passwords"
|
||||||
"git.julianfamily.org/keepassgo/internal/session"
|
"git.julianfamily.org/keepassgo/internal/session"
|
||||||
@@ -372,10 +373,10 @@ func TestUIHeaderMenusUseOverlayModelAcrossModes(t *testing.T) {
|
|||||||
func TestAnchoredMenuXAllowsWiderMenusToExtendLeft(t *testing.T) {
|
func TestAnchoredMenuXAllowsWiderMenusToExtendLeft(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
if got := anchoredMenuX(48, 160); got != -112 {
|
if got := appuilayout.AnchoredMenuX(48, 160); got != -112 {
|
||||||
t.Fatalf("anchoredMenuX(48, 160) = %d, want -112", got)
|
t.Fatalf("anchoredMenuX(48, 160) = %d, want -112", got)
|
||||||
}
|
}
|
||||||
if got := anchoredMenuX(160, 48); got != 112 {
|
if got := appuilayout.AnchoredMenuX(160, 48); got != 112 {
|
||||||
t.Fatalf("anchoredMenuX(160, 48) = %d, want 112", got)
|
t.Fatalf("anchoredMenuX(160, 48) = %d, want 112", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -383,10 +384,10 @@ func TestAnchoredMenuXAllowsWiderMenusToExtendLeft(t *testing.T) {
|
|||||||
func TestAnchoredMenuOriginXClampsToVisibleContainer(t *testing.T) {
|
func TestAnchoredMenuOriginXClampsToVisibleContainer(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
if got := anchoredMenuOriginX(360, 312, 360, 140); got != 220 {
|
if got := appuilayout.AnchoredMenuOriginX(360, 312, 360, 140); got != 220 {
|
||||||
t.Fatalf("anchoredMenuOriginX should keep a right-aligned menu visible, got %d want 220", got)
|
t.Fatalf("anchoredMenuOriginX should keep a right-aligned menu visible, got %d want 220", got)
|
||||||
}
|
}
|
||||||
if got := anchoredMenuOriginX(360, 0, 44, 160); got != 0 {
|
if got := appuilayout.AnchoredMenuOriginX(360, 0, 44, 160); got != 0 {
|
||||||
t.Fatalf("anchoredMenuOriginX should clamp oversized left overflow to zero, got %d want 0", got)
|
t.Fatalf("anchoredMenuOriginX should clamp oversized left overflow to zero, got %d want 0", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -394,7 +395,7 @@ func TestAnchoredMenuOriginXClampsToVisibleContainer(t *testing.T) {
|
|||||||
func TestHeaderActionMetricsComputeTriggerAnchors(t *testing.T) {
|
func TestHeaderActionMetricsComputeTriggerAnchors(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
metrics := headerActionMetrics{
|
metrics := appuilayout.HeaderActionMetrics{
|
||||||
RowOriginX: 24,
|
RowOriginX: 24,
|
||||||
Spacing: 8,
|
Spacing: 8,
|
||||||
RowDims: layout.Dimensions{Size: image.Pt(180, 40)},
|
RowDims: layout.Dimensions{Size: image.Pt(180, 40)},
|
||||||
@@ -403,10 +404,10 @@ func TestHeaderActionMetricsComputeTriggerAnchors(t *testing.T) {
|
|||||||
MainDims: layout.Dimensions{Size: image.Pt(36, 40)},
|
MainDims: layout.Dimensions{Size: image.Pt(36, 40)},
|
||||||
}
|
}
|
||||||
|
|
||||||
if got := metrics.syncAnchor(); got != (dropdownAnchor{TriggerRightX: 76, TriggerBottomY: 40}) {
|
if got := metrics.SyncAnchor(); got != (appuilayout.DropdownAnchor{TriggerRightX: 76, TriggerBottomY: 40}) {
|
||||||
t.Fatalf("metrics.syncAnchor() = %+v, want right=76 bottom=40", got)
|
t.Fatalf("metrics.syncAnchor() = %+v, want right=76 bottom=40", got)
|
||||||
}
|
}
|
||||||
if got := metrics.mainAnchor(); got != (dropdownAnchor{TriggerRightX: 172, TriggerBottomY: 40}) {
|
if got := metrics.MainAnchor(); got != (appuilayout.DropdownAnchor{TriggerRightX: 172, TriggerBottomY: 40}) {
|
||||||
t.Fatalf("metrics.mainAnchor() = %+v, want right=172 bottom=40", got)
|
t.Fatalf("metrics.mainAnchor() = %+v, want right=172 bottom=40", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -414,15 +415,15 @@ func TestHeaderActionMetricsComputeTriggerAnchors(t *testing.T) {
|
|||||||
func TestDropdownSurfaceOriginKeepsMenusWithinVisibleArea(t *testing.T) {
|
func TestDropdownSurfaceOriginKeepsMenusWithinVisibleArea(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
surface := dropdownSurface{ContainerWidth: 320, LeftInset: 16, TopInset: 16}
|
surface := appuilayout.DropdownSurface{ContainerWidth: 320, LeftInset: 16, TopInset: 16}
|
||||||
anchor := dropdownAnchor{TriggerRightX: 300, TriggerBottomY: 42}
|
anchor := appuilayout.DropdownAnchor{TriggerRightX: 300, TriggerBottomY: 42}
|
||||||
|
|
||||||
if got := surface.origin(anchor, 140); got != image.Pt(176, 58) {
|
if got := surface.Origin(anchor, 140); got != image.Pt(176, 58) {
|
||||||
t.Fatalf("surface.origin(anchor, 140) = %v, want (176,58)", got)
|
t.Fatalf("surface.origin(anchor, 140) = %v, want (176,58)", got)
|
||||||
}
|
}
|
||||||
|
|
||||||
leftAnchor := dropdownAnchor{TriggerRightX: 36, TriggerBottomY: 42}
|
leftAnchor := appuilayout.DropdownAnchor{TriggerRightX: 36, TriggerBottomY: 42}
|
||||||
if got := surface.origin(leftAnchor, 120); got != image.Pt(16, 58) {
|
if got := surface.Origin(leftAnchor, 120); got != image.Pt(16, 58) {
|
||||||
t.Fatalf("surface.origin(leftAnchor, 120) = %v, want (16,58)", got)
|
t.Fatalf("surface.origin(leftAnchor, 120) = %v, want (16,58)", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -438,14 +439,14 @@ func TestBuildSyncMenuModelForUnboundVault(t *testing.T) {
|
|||||||
u.state.Section = appstate.SectionEntries
|
u.state.Section = appstate.SectionEntries
|
||||||
|
|
||||||
model := u.buildSyncMenuModel()
|
model := u.buildSyncMenuModel()
|
||||||
if !model.showRemoteSyncSetupShortcut() {
|
if !model.ShowRemoteSyncSetupShortcut() {
|
||||||
t.Fatal("model.showRemoteSyncSetupShortcut() = false, want true for an unbound open vault")
|
t.Fatal("model.ShowRemoteSyncSetupShortcut() = false, want true for an unbound open vault")
|
||||||
}
|
}
|
||||||
if model.showDirectRemoteSyncShortcut() {
|
if model.ShowDirectRemoteSyncShortcut() {
|
||||||
t.Fatal("model.showDirectRemoteSyncShortcut() = true, want false without a saved binding")
|
t.Fatal("model.ShowDirectRemoteSyncShortcut() = true, want false without a saved binding")
|
||||||
}
|
}
|
||||||
if got := model.actionLabels(); !slices.Equal(got, []string{"Open Advanced Sync", "Set Up Remote Sync"}) {
|
if got := model.ActionLabels(); !slices.Equal(got, []string{"Open Advanced Sync", "Set Up Remote Sync"}) {
|
||||||
t.Fatalf("model.actionLabels() = %v, want [Open Advanced Sync Set Up Remote Sync]", got)
|
t.Fatalf("model.ActionLabels() = %v, want [Open Advanced Sync Set Up Remote Sync]", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,30 +477,30 @@ func TestBuildSyncMenuModelForBoundVault(t *testing.T) {
|
|||||||
u.selectedVaultRemoteSyncMode = appstate.SyncModeAutomaticOnOpenSave
|
u.selectedVaultRemoteSyncMode = appstate.SyncModeAutomaticOnOpenSave
|
||||||
|
|
||||||
model := u.buildSyncMenuModel()
|
model := u.buildSyncMenuModel()
|
||||||
if model.showRemoteSyncSetupShortcut() {
|
if model.ShowRemoteSyncSetupShortcut() {
|
||||||
t.Fatal("model.showRemoteSyncSetupShortcut() = true, want false for a bound vault")
|
t.Fatal("model.ShowRemoteSyncSetupShortcut() = true, want false for a bound vault")
|
||||||
}
|
}
|
||||||
if !model.showDirectRemoteSyncShortcut() {
|
if !model.ShowDirectRemoteSyncShortcut() {
|
||||||
t.Fatal("model.showDirectRemoteSyncShortcut() = false, want true for a bound vault")
|
t.Fatal("model.ShowDirectRemoteSyncShortcut() = false, want true for a bound vault")
|
||||||
}
|
}
|
||||||
if !model.showRemoteSyncSettingsShortcut() {
|
if !model.ShowRemoteSyncSettingsShortcut() {
|
||||||
t.Fatal("model.showRemoteSyncSettingsShortcut() = false, want true for a bound vault")
|
t.Fatal("model.ShowRemoteSyncSettingsShortcut() = false, want true for a bound vault")
|
||||||
}
|
}
|
||||||
if !model.showRemoveRemoteSyncShortcut() {
|
if !model.ShowRemoveRemoteSyncShortcut() {
|
||||||
t.Fatal("model.showRemoveRemoteSyncShortcut() = false, want true for a bound vault")
|
t.Fatal("model.ShowRemoveRemoteSyncShortcut() = false, want true for a bound vault")
|
||||||
}
|
}
|
||||||
summary := model.savedBindingSummary
|
summary := model.SavedBindingSummary
|
||||||
if !summary.ok {
|
if !summary.OK {
|
||||||
t.Fatal("model.savedBindingSummary.ok = false, want true")
|
t.Fatal("model.SavedBindingSummary.OK = false, want true")
|
||||||
}
|
}
|
||||||
if summary.profileLabel != "Downtown Mint" {
|
if summary.ProfileLabel != "Downtown Mint" {
|
||||||
t.Fatalf("model.savedBindingSummary.profileLabel = %q, want Downtown Mint", summary.profileLabel)
|
t.Fatalf("model.SavedBindingSummary.ProfileLabel = %q, want Downtown Mint", summary.ProfileLabel)
|
||||||
}
|
}
|
||||||
if summary.credentialLabel != "Mint Credentials · verbal-kint" {
|
if summary.CredentialLabel != "Mint Credentials · verbal-kint" {
|
||||||
t.Fatalf("model.savedBindingSummary.credentialLabel = %q, want Mint Credentials · verbal-kint", summary.credentialLabel)
|
t.Fatalf("model.SavedBindingSummary.CredentialLabel = %q, want Mint Credentials · verbal-kint", summary.CredentialLabel)
|
||||||
}
|
}
|
||||||
if summary.syncLabel != "Syncs automatically on open and save." {
|
if summary.SyncLabel != "Syncs automatically on open and save." {
|
||||||
t.Fatalf("model.savedBindingSummary.syncLabel = %q, want automatic-sync summary", summary.syncLabel)
|
t.Fatalf("model.SavedBindingSummary.SyncLabel = %q, want automatic-sync summary", summary.SyncLabel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,8 +515,8 @@ func TestBuildSyncMenuModelShowsSaveCurrentBindingOnlyWithCompleteRemoteInput(t
|
|||||||
u.state.Section = appstate.SectionEntries
|
u.state.Section = appstate.SectionEntries
|
||||||
|
|
||||||
model := u.buildSyncMenuModel()
|
model := u.buildSyncMenuModel()
|
||||||
if model.showSaveCurrentBinding {
|
if model.ShowSaveCurrentBinding {
|
||||||
t.Fatal("model.showSaveCurrentBinding = true, want false without remote input")
|
t.Fatal("model.ShowSaveCurrentBinding = true, want false without remote input")
|
||||||
}
|
}
|
||||||
|
|
||||||
u.remoteBaseURL.SetText("https://mint.example.invalid/remote.php/dav")
|
u.remoteBaseURL.SetText("https://mint.example.invalid/remote.php/dav")
|
||||||
@@ -524,14 +525,14 @@ func TestBuildSyncMenuModelShowsSaveCurrentBindingOnlyWithCompleteRemoteInput(t
|
|||||||
u.remotePassword.SetText("kobayashi")
|
u.remotePassword.SetText("kobayashi")
|
||||||
|
|
||||||
model = u.buildSyncMenuModel()
|
model = u.buildSyncMenuModel()
|
||||||
if !model.showSaveCurrentBinding {
|
if !model.ShowSaveCurrentBinding {
|
||||||
t.Fatal("model.showSaveCurrentBinding = false, want true with complete remote input")
|
t.Fatal("model.ShowSaveCurrentBinding = false, want true with complete remote input")
|
||||||
}
|
}
|
||||||
if got := model.saveCurrentRemoteBindingHeading(); got != "Bind this local vault to the current remote target" {
|
if got := model.SaveCurrentRemoteBindingHeading(); got != "Bind this local vault to the current remote target" {
|
||||||
t.Fatalf("model.saveCurrentRemoteBindingHeading() = %q, want vault binding guidance", got)
|
t.Fatalf("model.SaveCurrentRemoteBindingHeading() = %q, want vault binding guidance", got)
|
||||||
}
|
}
|
||||||
if got := model.saveCurrentRemoteBindingButtonLabel(); got != "Save Remote In Vault" {
|
if got := model.SaveCurrentRemoteBindingButtonLabel(); got != "Save Remote In Vault" {
|
||||||
t.Fatalf("model.saveCurrentRemoteBindingButtonLabel() = %q, want Save Remote In Vault", got)
|
t.Fatalf("model.SaveCurrentRemoteBindingButtonLabel() = %q, want Save Remote In Vault", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
//go:build android
|
//go:build android
|
||||||
|
|
||||||
package appui
|
package platform
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo CFLAGS: -Werror
|
#cgo CFLAGS: -Werror
|
||||||
@@ -71,7 +71,7 @@ func gioJavaVM() *C.JavaVM
|
|||||||
//go:linkname gioRunInJVM gioui.org/app.runInJVM
|
//go:linkname gioRunInJVM gioui.org/app.runInJVM
|
||||||
func gioRunInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv))
|
func gioRunInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv))
|
||||||
|
|
||||||
func newPlatformVaultSharer(goos string) vaultSharer {
|
func NewVaultSharer(goos string) VaultSharer {
|
||||||
return androidVaultSharer{}
|
return androidVaultSharer{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build !android
|
||||||
|
|
||||||
|
package platform
|
||||||
|
|
||||||
|
func NewVaultSharer(goos string) VaultSharer {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package appui
|
package platform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
@@ -11,20 +11,24 @@ import (
|
|||||||
appclipboard "git.julianfamily.org/keepassgo/internal/clipboard"
|
appclipboard "git.julianfamily.org/keepassgo/internal/clipboard"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type VaultSharer interface {
|
||||||
|
ShareVault(path, title string) error
|
||||||
|
}
|
||||||
|
|
||||||
type clipboardCommandWriter struct {
|
type clipboardCommandWriter struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
pending []string
|
pending []string
|
||||||
invalidate func()
|
invalidate func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPlatformClipboardWriter(goos string, invalidate func()) appclipboard.Writer {
|
func NewClipboardWriter(goos string, invalidate func()) appclipboard.Writer {
|
||||||
if strings.EqualFold(goos, "android") {
|
if strings.EqualFold(goos, "android") {
|
||||||
return &clipboardCommandWriter{invalidate: invalidate}
|
return &clipboardCommandWriter{invalidate: invalidate}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func processClipboardWrites(gtx layout.Context, writer appclipboard.Writer) {
|
func ProcessClipboardWrites(gtx layout.Context, writer appclipboard.Writer) {
|
||||||
commandWriter, ok := writer.(*clipboardCommandWriter)
|
commandWriter, ok := writer.(*clipboardCommandWriter)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
+5
-5
@@ -1,4 +1,4 @@
|
|||||||
package appui
|
package platform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"slices"
|
"slices"
|
||||||
@@ -8,17 +8,17 @@ import (
|
|||||||
func TestNewPlatformClipboardWriterUsesCommandWriterOnAndroid(t *testing.T) {
|
func TestNewPlatformClipboardWriterUsesCommandWriterOnAndroid(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
writer := newPlatformClipboardWriter("android", nil)
|
writer := NewClipboardWriter("android", nil)
|
||||||
if _, ok := writer.(*clipboardCommandWriter); !ok {
|
if _, ok := writer.(*clipboardCommandWriter); !ok {
|
||||||
t.Fatalf("newPlatformClipboardWriter(android) = %T, want *clipboardCommandWriter", writer)
|
t.Fatalf("NewClipboardWriter(android) = %T, want *clipboardCommandWriter", writer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewPlatformClipboardWriterUsesSystemClipboardOffAndroid(t *testing.T) {
|
func TestNewPlatformClipboardWriterUsesSystemClipboardOffAndroid(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
if writer := newPlatformClipboardWriter("linux", nil); writer != nil {
|
if writer := NewClipboardWriter("linux", nil); writer != nil {
|
||||||
t.Fatalf("newPlatformClipboardWriter(linux) = %T, want nil", writer)
|
t.Fatalf("NewClipboardWriter(linux) = %T, want nil", writer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
"gioui.org/widget"
|
"gioui.org/widget"
|
||||||
"gioui.org/widget/material"
|
"gioui.org/widget/material"
|
||||||
|
appuilayout "git.julianfamily.org/keepassgo/internal/appui/layout"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (u *ui) header(gtx layout.Context) layout.Dimensions {
|
func (u *ui) header(gtx layout.Context) layout.Dimensions {
|
||||||
@@ -44,7 +45,7 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions {
|
|||||||
return layout.Dimensions{}
|
return layout.Dimensions{}
|
||||||
}
|
}
|
||||||
spacing := gtx.Dp(unit.Dp(8))
|
spacing := gtx.Dp(unit.Dp(8))
|
||||||
metrics := headerActionMetrics{Spacing: spacing}
|
metrics := appuilayout.HeaderActionMetrics{Spacing: spacing}
|
||||||
row := func(gtx layout.Context) layout.Dimensions {
|
row := 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 {
|
||||||
@@ -73,7 +74,7 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions {
|
|||||||
metrics.RowOriginX = max(0, gtx.Constraints.Max.X-metrics.RowDims.Size.X)
|
metrics.RowOriginX = max(0, gtx.Constraints.Max.X-metrics.RowDims.Size.X)
|
||||||
}
|
}
|
||||||
|
|
||||||
surface := dropdownSurface{ContainerWidth: gtx.Constraints.Max.X, LeftInset: 0, TopInset: 0}
|
surface := appuilayout.DropdownSurface{ContainerWidth: gtx.Constraints.Max.X, LeftInset: 0, TopInset: 0}
|
||||||
|
|
||||||
rowStack := op.Offset(image.Pt(metrics.RowOriginX, 0)).Push(gtx.Ops)
|
rowStack := op.Offset(image.Pt(metrics.RowOriginX, 0)).Push(gtx.Ops)
|
||||||
rowCall.Add(gtx.Ops)
|
rowCall.Add(gtx.Ops)
|
||||||
@@ -82,21 +83,21 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions {
|
|||||||
if u.usesCompactViewport() {
|
if u.usesCompactViewport() {
|
||||||
if u.syncMenuOpen {
|
if u.syncMenuOpen {
|
||||||
u.phoneSyncMenuVisible = true
|
u.phoneSyncMenuVisible = true
|
||||||
u.phoneSyncMenuAnchor = metrics.syncAnchor().point()
|
u.phoneSyncMenuAnchor = metrics.SyncAnchor().Point()
|
||||||
}
|
}
|
||||||
if u.mainMenuOpen {
|
if u.mainMenuOpen {
|
||||||
u.phoneMainMenuVisible = true
|
u.phoneMainMenuVisible = true
|
||||||
u.phoneMainMenuAnchor = metrics.mainAnchor().point()
|
u.phoneMainMenuAnchor = metrics.MainAnchor().Point()
|
||||||
}
|
}
|
||||||
width := gtx.Constraints.Max.X
|
width := gtx.Constraints.Max.X
|
||||||
return layout.Dimensions{Size: image.Pt(width, metrics.RowDims.Size.Y)}
|
return layout.Dimensions{Size: image.Pt(width, metrics.RowDims.Size.Y)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if u.syncMenuOpen {
|
if u.syncMenuOpen {
|
||||||
surface.draw(gtx, metrics.syncAnchor(), u.syncMenu)
|
surface.Draw(gtx, metrics.SyncAnchor(), u.syncMenu)
|
||||||
}
|
}
|
||||||
if u.mainMenuOpen {
|
if u.mainMenuOpen {
|
||||||
surface.draw(gtx, metrics.mainAnchor(), u.mainMenu)
|
surface.Draw(gtx, metrics.MainAnchor(), u.mainMenu)
|
||||||
}
|
}
|
||||||
|
|
||||||
width := metrics.RowDims.Size.X
|
width := metrics.RowDims.Size.X
|
||||||
@@ -205,29 +206,29 @@ func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions {
|
|||||||
return tonedButton(gtx, u.theme, &u.openAdvancedSync, "Open Advanced Sync")
|
return tonedButton(gtx, u.theme, &u.openAdvancedSync, "Open Advanced Sync")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if model.showShare {
|
if model.ShowShare {
|
||||||
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &u.shareCurrentVault, "Share Vault")
|
return tonedButton(gtx, u.theme, &u.shareCurrentVault, "Share Vault")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if model.showRemoteSyncSetupShortcut() {
|
if model.ShowRemoteSyncSetupShortcut() {
|
||||||
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &u.useSavedAdvancedSyncRemote, model.remoteSyncSetupShortcutLabel())
|
return tonedButton(gtx, u.theme, &u.useSavedAdvancedSyncRemote, model.RemoteSyncSetupShortcutLabel())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if model.showDirectRemoteSyncShortcut() {
|
if model.ShowDirectRemoteSyncShortcut() {
|
||||||
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &u.openSelectedVaultRemote, model.directRemoteSyncShortcutLabel())
|
return tonedButton(gtx, u.theme, &u.openSelectedVaultRemote, model.DirectRemoteSyncShortcutLabel())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if model.showRemoteSyncSettingsShortcut() {
|
if model.ShowRemoteSyncSettingsShortcut() {
|
||||||
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &u.useSavedAdvancedSyncRemote, model.remoteSyncSettingsShortcutLabel())
|
return tonedButton(gtx, u.theme, &u.useSavedAdvancedSyncRemote, model.RemoteSyncSettingsShortcutLabel())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if model.showRemoveRemoteSyncShortcut() {
|
if model.ShowRemoveRemoteSyncShortcut() {
|
||||||
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &u.removeSelectedRemoteBinding, model.removeRemoteSyncShortcutLabel())
|
return tonedButton(gtx, u.theme, &u.removeSelectedRemoteBinding, model.RemoveRemoteSyncShortcutLabel())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
actionWidth := menuActionWidth(gtx, actionRows)
|
actionWidth := menuActionWidth(gtx, actionRows)
|
||||||
@@ -240,7 +241,7 @@ func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions {
|
|||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Height: 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 {
|
||||||
if !model.showShare {
|
if !model.ShowShare {
|
||||||
return layout.Dimensions{}
|
return layout.Dimensions{}
|
||||||
}
|
}
|
||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
@@ -258,42 +259,42 @@ func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions {
|
|||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
if model.showRemoteSyncSetupShortcut() {
|
if model.ShowRemoteSyncSetupShortcut() {
|
||||||
rows = append(rows,
|
rows = append(rows,
|
||||||
layout.Rigid(layout.Spacer{Height: 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 rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
return rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &u.useSavedAdvancedSyncRemote, model.remoteSyncSetupShortcutLabel())
|
return tonedButton(gtx, u.theme, &u.useSavedAdvancedSyncRemote, model.RemoteSyncSetupShortcutLabel())
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if model.showDirectRemoteSyncShortcut() {
|
if model.ShowDirectRemoteSyncShortcut() {
|
||||||
rows = append(rows,
|
rows = append(rows,
|
||||||
layout.Rigid(layout.Spacer{Height: 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 rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
return rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &u.openSelectedVaultRemote, model.directRemoteSyncShortcutLabel())
|
return tonedButton(gtx, u.theme, &u.openSelectedVaultRemote, model.DirectRemoteSyncShortcutLabel())
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if model.showRemoteSyncSettingsShortcut() {
|
if model.ShowRemoteSyncSettingsShortcut() {
|
||||||
rows = append(rows,
|
rows = append(rows,
|
||||||
layout.Rigid(layout.Spacer{Height: 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 rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
return rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &u.useSavedAdvancedSyncRemote, model.remoteSyncSettingsShortcutLabel())
|
return tonedButton(gtx, u.theme, &u.useSavedAdvancedSyncRemote, model.RemoteSyncSettingsShortcutLabel())
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if model.showRemoveRemoteSyncShortcut() {
|
if model.ShowRemoveRemoteSyncShortcut() {
|
||||||
rows = append(rows,
|
rows = append(rows,
|
||||||
layout.Rigid(layout.Spacer{Height: 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 rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
return rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &u.removeSelectedRemoteBinding, model.removeRemoteSyncShortcutLabel())
|
return tonedButton(gtx, u.theme, &u.removeSelectedRemoteBinding, model.RemoveRemoteSyncShortcutLabel())
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -302,36 +303,36 @@ func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions {
|
|||||||
rows = append(rows,
|
rows = append(rows,
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.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(11), model.savedBindingHeading())
|
lbl := material.Label(u.theme, unit.Sp(11), model.SavedBindingHeading())
|
||||||
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),
|
||||||
)
|
)
|
||||||
if !model.showSelectors {
|
if !model.ShowSelectors {
|
||||||
rows = append(rows,
|
rows = append(rows,
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
summary := model.savedBindingSummary
|
summary := model.SavedBindingSummary
|
||||||
if !summary.ok {
|
if !summary.OK {
|
||||||
return layout.Dimensions{}
|
return layout.Dimensions{}
|
||||||
}
|
}
|
||||||
return layout.Background{}.Layout(gtx, fill(color.NRGBA{R: 242, G: 245, B: 240, A: 255}), func(gtx layout.Context) layout.Dimensions {
|
return layout.Background{}.Layout(gtx, fill(color.NRGBA{R: 242, G: 245, B: 240, A: 255}), func(gtx layout.Context) layout.Dimensions {
|
||||||
return layout.UniformInset(unit.Dp(10)).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,
|
return layout.Flex{Axis: layout.Vertical}.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(13), summary.profileLabel)
|
lbl := material.Label(u.theme, unit.Sp(13), summary.ProfileLabel)
|
||||||
lbl.Color = accentColor
|
lbl.Color = accentColor
|
||||||
return lbl.Layout(gtx)
|
return lbl.Layout(gtx)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(2)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(2)}.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), "Credential: "+summary.credentialLabel)
|
lbl := material.Label(u.theme, unit.Sp(12), "Credential: "+summary.CredentialLabel)
|
||||||
lbl.Color = mutedColor
|
lbl.Color = mutedColor
|
||||||
return lbl.Layout(gtx)
|
return lbl.Layout(gtx)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(2)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(2)}.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), summary.syncLabel)
|
lbl := material.Label(u.theme, unit.Sp(12), summary.SyncLabel)
|
||||||
lbl.Color = mutedColor
|
lbl.Color = mutedColor
|
||||||
return lbl.Layout(gtx)
|
return lbl.Layout(gtx)
|
||||||
}),
|
}),
|
||||||
@@ -394,17 +395,17 @@ func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if model.showSaveCurrentBinding {
|
if model.ShowSaveCurrentBinding {
|
||||||
rows = append(rows,
|
rows = append(rows,
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.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(11), model.saveCurrentRemoteBindingHeading())
|
lbl := material.Label(u.theme, unit.Sp(11), model.SaveCurrentRemoteBindingHeading())
|
||||||
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),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &u.saveCurrentRemoteBinding, model.saveCurrentRemoteBindingButtonLabel())
|
return tonedButton(gtx, u.theme, &u.saveCurrentRemoteBinding, model.SaveCurrentRemoteBindingButtonLabel())
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -468,17 +469,17 @@ func (u *ui) phoneHeaderMenus(gtx layout.Context) layout.Dimensions {
|
|||||||
}
|
}
|
||||||
gtx.Constraints.Min = gtx.Constraints.Max
|
gtx.Constraints.Min = gtx.Constraints.Max
|
||||||
contentInsetPx := gtx.Dp(unit.Dp(16))
|
contentInsetPx := gtx.Dp(unit.Dp(16))
|
||||||
surface := dropdownSurface{
|
surface := appuilayout.DropdownSurface{
|
||||||
ContainerWidth: max(0, gtx.Constraints.Max.X-(contentInsetPx*2)),
|
ContainerWidth: max(0, gtx.Constraints.Max.X-(contentInsetPx*2)),
|
||||||
LeftInset: contentInsetPx,
|
LeftInset: contentInsetPx,
|
||||||
TopInset: contentInsetPx,
|
TopInset: contentInsetPx,
|
||||||
}
|
}
|
||||||
|
|
||||||
if u.syncMenuVisibleOnPhone() {
|
if u.syncMenuVisibleOnPhone() {
|
||||||
surface.draw(gtx, dropdownAnchor{TriggerRightX: u.phoneSyncMenuAnchor.X, TriggerBottomY: u.phoneSyncMenuAnchor.Y}, u.syncMenu)
|
surface.Draw(gtx, appuilayout.DropdownAnchor{TriggerRightX: u.phoneSyncMenuAnchor.X, TriggerBottomY: u.phoneSyncMenuAnchor.Y}, u.syncMenu)
|
||||||
}
|
}
|
||||||
if u.mainMenuVisibleOnPhone() {
|
if u.mainMenuVisibleOnPhone() {
|
||||||
surface.draw(gtx, dropdownAnchor{TriggerRightX: u.phoneMainMenuAnchor.X, TriggerBottomY: u.phoneMainMenuAnchor.Y}, u.mainMenu)
|
surface.Draw(gtx, appuilayout.DropdownAnchor{TriggerRightX: u.phoneMainMenuAnchor.X, TriggerBottomY: u.phoneMainMenuAnchor.Y}, u.mainMenu)
|
||||||
}
|
}
|
||||||
return layout.Dimensions{Size: gtx.Constraints.Max}
|
return layout.Dimensions{Size: gtx.Constraints.Max}
|
||||||
}
|
}
|
||||||
@@ -511,21 +512,6 @@ func (u *ui) mainMenuRightAlignsToTrigger() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func anchoredMenuX(triggerWidth, menuWidth int) int {
|
|
||||||
return triggerWidth - menuWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
func anchoredMenuOriginX(containerWidth, rowOriginX, triggerRightX, menuWidth int) int {
|
|
||||||
x := rowOriginX + triggerRightX - menuWidth
|
|
||||||
if x < 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
if x+menuWidth > containerWidth {
|
|
||||||
return max(0, containerWidth-menuWidth)
|
|
||||||
}
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
func menuActionWidth(gtx layout.Context, rows []layout.Widget) int {
|
func menuActionWidth(gtx layout.Context, rows []layout.Widget) int {
|
||||||
width := 0
|
width := 0
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package appui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.julianfamily.org/keepassgo/internal/appstate"
|
||||||
|
appuiactions "git.julianfamily.org/keepassgo/internal/appui/actions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (u *ui) buildSyncMenuModel() appuiactions.SyncMenuModel {
|
||||||
|
model := appuiactions.SyncMenuModel{
|
||||||
|
HasOpenVault: u.hasOpenVault(),
|
||||||
|
ShowSelectors: u.shouldShowSavedRemoteBindingSelectors(),
|
||||||
|
ShowShare: supportsVaultShare(runtime.GOOS) && u.vaultSharer != nil && strings.TrimSpace(u.currentShareableVaultPath()) != "",
|
||||||
|
RemoteBaseURL: strings.TrimSpace(u.remoteBaseURL.Text()),
|
||||||
|
RemotePath: strings.TrimSpace(u.remotePath.Text()),
|
||||||
|
RemoteUsername: strings.TrimSpace(u.remoteUsername.Text()),
|
||||||
|
RemotePassword: u.remotePassword.Text(),
|
||||||
|
SelectedVaultSyncMode: normalizeUISyncMode(u.selectedVaultRemoteSyncMode),
|
||||||
|
}
|
||||||
|
_, model.HasSelectedBinding = u.selectedVaultRemoteBinding()
|
||||||
|
model.SavedBindingSummary = u.computeSavedRemoteBindingSummary()
|
||||||
|
model.ShowSaveCurrentBinding = model.HasOpenVault && model.RemoteBaseURL != "" && model.RemotePath != "" && model.RemoteUsername != "" && model.RemotePassword != ""
|
||||||
|
return model
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) computeSavedRemoteBindingSummary() appuiactions.SyncMenuBindingSummary {
|
||||||
|
profile, ok := u.selectedVaultRemoteProfile()
|
||||||
|
if !ok {
|
||||||
|
return appuiactions.SyncMenuBindingSummary{}
|
||||||
|
}
|
||||||
|
entry, ok := u.selectedVaultRemoteCredentialEntry()
|
||||||
|
if !ok {
|
||||||
|
return appuiactions.SyncMenuBindingSummary{}
|
||||||
|
}
|
||||||
|
credentialLabel := entry.Title
|
||||||
|
if strings.TrimSpace(entry.Username) != "" {
|
||||||
|
credentialLabel += " · " + strings.TrimSpace(entry.Username)
|
||||||
|
}
|
||||||
|
syncLabel := "Sync manually when you choose Use Remote Sync."
|
||||||
|
if normalizeUISyncMode(u.selectedVaultRemoteSyncMode) == appstate.SyncModeAutomaticOnOpenSave {
|
||||||
|
syncLabel = "Syncs automatically on open and save."
|
||||||
|
}
|
||||||
|
return appuiactions.SyncMenuBindingSummary{
|
||||||
|
ProfileLabel: profile.Name,
|
||||||
|
CredentialLabel: credentialLabel,
|
||||||
|
SyncLabel: syncLabel,
|
||||||
|
OK: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
package appui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.julianfamily.org/keepassgo/internal/appstate"
|
|
||||||
)
|
|
||||||
|
|
||||||
type syncMenuModel struct {
|
|
||||||
hasOpenVault bool
|
|
||||||
hasSelectedBinding bool
|
|
||||||
showSelectors bool
|
|
||||||
showShare bool
|
|
||||||
showSaveCurrentBinding bool
|
|
||||||
savedBindingSummary syncMenuBindingSummary
|
|
||||||
remoteBaseURL string
|
|
||||||
remotePath string
|
|
||||||
remoteUsername string
|
|
||||||
remotePassword string
|
|
||||||
selectedVaultSyncMode appstate.SyncMode
|
|
||||||
}
|
|
||||||
|
|
||||||
type syncMenuBindingSummary struct {
|
|
||||||
profileLabel string
|
|
||||||
credentialLabel string
|
|
||||||
syncLabel string
|
|
||||||
ok bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ui) buildSyncMenuModel() syncMenuModel {
|
|
||||||
model := syncMenuModel{
|
|
||||||
hasOpenVault: u.hasOpenVault(),
|
|
||||||
showSelectors: u.shouldShowSavedRemoteBindingSelectors(),
|
|
||||||
showShare: supportsVaultShare(runtime.GOOS) && u.vaultSharer != nil && strings.TrimSpace(u.currentShareableVaultPath()) != "",
|
|
||||||
remoteBaseURL: strings.TrimSpace(u.remoteBaseURL.Text()),
|
|
||||||
remotePath: strings.TrimSpace(u.remotePath.Text()),
|
|
||||||
remoteUsername: strings.TrimSpace(u.remoteUsername.Text()),
|
|
||||||
remotePassword: u.remotePassword.Text(),
|
|
||||||
selectedVaultSyncMode: normalizeUISyncMode(u.selectedVaultRemoteSyncMode),
|
|
||||||
}
|
|
||||||
_, model.hasSelectedBinding = u.selectedVaultRemoteBinding()
|
|
||||||
model.savedBindingSummary = u.computeSavedRemoteBindingSummary()
|
|
||||||
model.showSaveCurrentBinding = model.hasOpenVault && model.remoteBaseURL != "" && model.remotePath != "" && model.remoteUsername != "" && model.remotePassword != ""
|
|
||||||
return model
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ui) computeSavedRemoteBindingSummary() syncMenuBindingSummary {
|
|
||||||
profile, ok := u.selectedVaultRemoteProfile()
|
|
||||||
if !ok {
|
|
||||||
return syncMenuBindingSummary{}
|
|
||||||
}
|
|
||||||
entry, ok := u.selectedVaultRemoteCredentialEntry()
|
|
||||||
if !ok {
|
|
||||||
return syncMenuBindingSummary{}
|
|
||||||
}
|
|
||||||
credentialLabel := entry.Title
|
|
||||||
if strings.TrimSpace(entry.Username) != "" {
|
|
||||||
credentialLabel += " · " + strings.TrimSpace(entry.Username)
|
|
||||||
}
|
|
||||||
syncLabel := "Sync manually when you choose Use Remote Sync."
|
|
||||||
if normalizeUISyncMode(u.selectedVaultRemoteSyncMode) == appstate.SyncModeAutomaticOnOpenSave {
|
|
||||||
syncLabel = "Syncs automatically on open and save."
|
|
||||||
}
|
|
||||||
return syncMenuBindingSummary{
|
|
||||||
profileLabel: profile.Name,
|
|
||||||
credentialLabel: credentialLabel,
|
|
||||||
syncLabel: syncLabel,
|
|
||||||
ok: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m syncMenuModel) savedBindingHeading() string {
|
|
||||||
if !m.showSelectors {
|
|
||||||
return "Use this vault's saved remote sync target"
|
|
||||||
}
|
|
||||||
return "Use a saved remote profile from this vault"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m syncMenuModel) openSelectedButtonLabel() string {
|
|
||||||
if !m.showSelectors {
|
|
||||||
return "Use Remote Sync"
|
|
||||||
}
|
|
||||||
return "Open Saved Remote"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m syncMenuModel) showDirectRemoteSyncShortcut() bool {
|
|
||||||
return m.hasOpenVault && m.hasSelectedBinding
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m syncMenuModel) directRemoteSyncShortcutLabel() string {
|
|
||||||
return "Use Remote Sync"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m syncMenuModel) showRemoteSyncSettingsShortcut() bool {
|
|
||||||
return m.hasOpenVault && m.hasSelectedBinding
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m syncMenuModel) remoteSyncSettingsShortcutLabel() string {
|
|
||||||
return "Remote Sync Settings"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m syncMenuModel) showRemoveRemoteSyncShortcut() bool {
|
|
||||||
return m.showRemoteSyncSettingsShortcut()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m syncMenuModel) removeRemoteSyncShortcutLabel() string {
|
|
||||||
return "Stop Using Remote Sync"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m syncMenuModel) showRemoteSyncSetupShortcut() bool {
|
|
||||||
return m.hasOpenVault && !m.hasSelectedBinding
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m syncMenuModel) remoteSyncSetupShortcutLabel() string {
|
|
||||||
return "Set Up Remote Sync"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m syncMenuModel) actionLabels() []string {
|
|
||||||
labels := []string{"Open Advanced Sync"}
|
|
||||||
if m.showRemoteSyncSetupShortcut() {
|
|
||||||
labels = append(labels, m.remoteSyncSetupShortcutLabel())
|
|
||||||
}
|
|
||||||
if m.showDirectRemoteSyncShortcut() {
|
|
||||||
labels = append(labels, m.directRemoteSyncShortcutLabel())
|
|
||||||
}
|
|
||||||
if m.showRemoteSyncSettingsShortcut() {
|
|
||||||
labels = append(labels, m.remoteSyncSettingsShortcutLabel())
|
|
||||||
}
|
|
||||||
if m.showRemoveRemoteSyncShortcut() {
|
|
||||||
labels = append(labels, m.removeRemoteSyncShortcutLabel())
|
|
||||||
}
|
|
||||||
return labels
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m syncMenuModel) saveCurrentRemoteBindingHeading() string {
|
|
||||||
return "Bind this local vault to the current remote target"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m syncMenuModel) saveCurrentRemoteBindingButtonLabel() string {
|
|
||||||
return "Save Remote In Vault"
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user