Skip to content

Commit

Permalink
Merge pull request #20 from algorandfoundation/feat/prompt-for-destru…
Browse files Browse the repository at this point in the history
…ctive

feat: adds a confirmation modal before deleting key ENG-613
  • Loading branch information
HashMapsData2Value authored Nov 2, 2024
2 parents 96dbae0 + 9298e09 commit 3036470
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 31 deletions.
13 changes: 12 additions & 1 deletion internal/participation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ package internal
import (
"context"
"errors"
"github.com/algorandfoundation/hack-tui/api"
"time"

"github.com/algorandfoundation/hack-tui/api"
)

// GetPartKeys get the participation keys from the node
Expand Down Expand Up @@ -116,3 +117,13 @@ func DeletePartKey(ctx context.Context, client *api.ClientWithResponses, partici
}
return nil
}

// Removes a participation key from the list of keys
func RemovePartKeyByID(slice *[]api.ParticipationKey, id string) {
for i, item := range *slice {
if item.Id == id {
*slice = append((*slice)[:i], (*slice)[i+1:]...)
return
}
}
}
55 changes: 55 additions & 0 deletions internal/participation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,58 @@ func Test_DeleteParticipationKey(t *testing.T) {
t.Fatal(err)
}
}
func Test_RemovePartKeyByID(t *testing.T) {
// Test case: Remove an existing key
t.Run("Remove existing key", func(t *testing.T) {
keys := []api.ParticipationKey{
{Id: "key1"},
{Id: "key2"},
{Id: "key3"},
}
expectedKeys := []api.ParticipationKey{
{Id: "key1"},
{Id: "key3"},
}
RemovePartKeyByID(&keys, "key2")
if len(keys) != len(expectedKeys) {
t.Fatalf("expected %d keys, got %d", len(expectedKeys), len(keys))
}
for i, key := range keys {
if key.Id != expectedKeys[i].Id {
t.Fatalf("expected key ID %s, got %s", expectedKeys[i].Id, key.Id)
}
}
})

// Test case: Remove a non-existing key
t.Run("Remove non-existing key", func(t *testing.T) {
keys := []api.ParticipationKey{
{Id: "key1"},
{Id: "key2"},
{Id: "key3"},
}
expectedKeys := []api.ParticipationKey{
{Id: "key1"},
{Id: "key2"},
{Id: "key3"},
}
RemovePartKeyByID(&keys, "key4")
if len(keys) != len(expectedKeys) {
t.Fatalf("expected %d keys, got %d", len(expectedKeys), len(keys))
}
for i, key := range keys {
if key.Id != expectedKeys[i].Id {
t.Fatalf("expected key ID %s, got %s", expectedKeys[i].Id, key.Id)
}
}
})

// Test case: Remove a key from an empty list
t.Run("Remove key from empty list", func(t *testing.T) {
keys := []api.ParticipationKey{}
RemovePartKeyByID(&keys, "key1")
if len(keys) != 0 {
t.Fatalf("expected 0 keys, got %d", len(keys))
}
})
}
6 changes: 5 additions & 1 deletion ui/pages/accounts/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ func (m ViewModel) HandleMessage(msg tea.Msg) (ViewModel, tea.Cmd) {
case tea.KeyMsg:
switch msg.String() {
case "enter":
return m, EmitAccountSelected(m.SelectedAccount())
selAcc := m.SelectedAccount()
if selAcc != (internal.Account{}) {
return m, EmitAccountSelected(selAcc)
}
return m, nil
case "ctrl+c":
return m, tea.Quit
}
Expand Down
2 changes: 2 additions & 0 deletions ui/pages/keys/cmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (

type DeleteKey *api.ParticipationKey

type DeleteFinished string

func EmitDeleteKey(key *api.ParticipationKey) tea.Cmd {
return func() tea.Msg {
return DeleteKey(key)
Expand Down
32 changes: 29 additions & 3 deletions ui/pages/keys/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,41 @@ func (m ViewModel) HandleMessage(msg tea.Msg) (ViewModel, tea.Cmd) {
case internal.Account:
m.Address = msg.Address
m.table.SetRows(m.makeRows(m.Data))
case DeleteFinished:
if m.SelectedKeyToDelete == nil {
panic("SelectedKeyToDelete is unexpectedly nil")
}
internal.RemovePartKeyByID(m.Data, m.SelectedKeyToDelete.Id)
m.SelectedKeyToDelete = nil
m.table.SetRows(m.makeRows(m.Data))

case tea.KeyMsg:
switch msg.String() {
case "enter":
return m, EmitKeySelected(m.SelectedKey())
selKey := m.SelectedKey()
if selKey != nil {
return m, EmitKeySelected(selKey)
}
return m, nil
case "g":
// TODO: navigation

case "d":
return m, EmitDeleteKey(m.SelectedKey())
if m.SelectedKeyToDelete == nil {
m.SelectedKeyToDelete = m.SelectedKey()
} else {
m.SelectedKeyToDelete = nil
}
return m, nil
case "y": // "Yes do delete" option in the delete confirmation modal
if m.SelectedKeyToDelete != nil {
return m, EmitDeleteKey(m.SelectedKeyToDelete)
}
return m, nil
case "n": // "do NOT delete" option in the delete confirmation modal
if m.SelectedKeyToDelete != nil {
m.SelectedKeyToDelete = nil
}
return m, nil
case "ctrl+c":
return m, tea.Quit
}
Expand Down
5 changes: 4 additions & 1 deletion ui/pages/keys/model.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package keys

import (
"github.com/algorandfoundation/hack-tui/ui/style"
"sort"

"github.com/algorandfoundation/hack-tui/ui/style"

"github.com/algorandfoundation/hack-tui/api"
"github.com/algorandfoundation/hack-tui/ui/utils"
"github.com/charmbracelet/bubbles/table"
Expand All @@ -16,6 +17,8 @@ type ViewModel struct {
Width int
Height int

SelectedKeyToDelete *api.ParticipationKey

table table.Model
controls string
navigation string
Expand Down
22 changes: 22 additions & 0 deletions ui/pages/keys/view.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
package keys

import (
"fmt"

"github.com/algorandfoundation/hack-tui/api"
"github.com/algorandfoundation/hack-tui/ui/style"
"github.com/charmbracelet/lipgloss"
)

func (m ViewModel) View() string {
if m.SelectedKeyToDelete != nil {
modal := renderDeleteConfirmationModal(m.SelectedKeyToDelete)
overlay := lipgloss.Place(m.Width, m.Height, lipgloss.Center, lipgloss.Center, modal)
return overlay
}
table := style.ApplyBorder(m.Width, m.Height, "8").Render(m.table.View())
return style.WithNavigation(
m.navigation,
Expand All @@ -17,3 +26,16 @@ func (m ViewModel) View() string {
),
)
}

func renderDeleteConfirmationModal(partKey *api.ParticipationKey) string {
modalStyle := lipgloss.NewStyle().
Width(60).
Height(7).
Align(lipgloss.Center).
Border(lipgloss.RoundedBorder()).
Padding(1, 2)

modalContent := fmt.Sprintf("Participation Key: %v\nAccount Address: %v\nPress either y (yes) or n (no).", partKey.Id, partKey.Address)

return modalStyle.Render("Are you sure you want to delete this key from your node?\n" + modalContent)
}
57 changes: 32 additions & 25 deletions ui/viewport.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,13 @@ type ViewportViewModel struct {
errorPage ErrorViewModel
}

type DeleteFinished string

func DeleteKey(client *api.ClientWithResponses, key keys.DeleteKey) tea.Cmd {
return func() tea.Msg {
err := internal.DeletePartKey(context.Background(), client, key.Id)
if err != nil {
return DeleteFinished(err.Error())
return keys.DeleteFinished(err.Error())
}
return DeleteFinished("Key deleted")
return keys.DeleteFinished(key.Id)
}
}

Expand Down Expand Up @@ -99,8 +97,6 @@ func (m ViewportViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.page = KeysPage
case keys.DeleteKey:
return m, DeleteKey(m.client, msg)
case DeleteFinished:
// TODO
case tea.KeyMsg:
switch msg.String() {
// Tab Backwards
Expand All @@ -118,42 +114,53 @@ func (m ViewportViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Tab Forwards
case "tab":
if m.page == AccountsPage {
m.page = KeysPage
return m, accounts.EmitAccountSelected(m.accountsPage.SelectedAccount())
selAcc := m.accountsPage.SelectedAccount()
if selAcc != (internal.Account{}) {
m.page = KeysPage
return m, accounts.EmitAccountSelected(selAcc)
}
return m, nil
}
if m.page == KeysPage {
m.page = TransactionPage
// If there isn't a key already, select the first record
if m.keysPage.SelectedKey() == nil && m.Data != nil {
data := *m.Data.ParticipationKeys
return m, keys.EmitKeySelected(&data[0])
selKey := m.keysPage.SelectedKey()
if selKey != nil {
m.page = TransactionPage
return m, keys.EmitKeySelected(selKey)
}
// Navigate to the transaction page
return m, keys.EmitKeySelected(m.keysPage.SelectedKey())
}
return m, nil
case "a":
m.page = AccountsPage
case "g":
m.generatePage.Inputs[0].SetValue(m.accountsPage.SelectedAccount().Address)
m.page = GeneratePage
return m, nil
case "a":
m.page = AccountsPage
case "k":
m.page = KeysPage
return m, accounts.EmitAccountSelected(m.accountsPage.SelectedAccount())
selAcc := m.accountsPage.SelectedAccount()
if selAcc != (internal.Account{}) {
m.page = KeysPage
return m, accounts.EmitAccountSelected(selAcc)
}
return m, nil
case "t":
m.page = TransactionPage
// If there isn't a key already, select the first record for that account
if m.keysPage.SelectedKey() == nil && m.Data != nil {
data := *m.Data.ParticipationKeys
if m.page == AccountsPage {
acct := m.accountsPage.SelectedAccount()
data := *m.Data.ParticipationKeys
for i, key := range data {
if key.Address == acct.Address {
m.page = TransactionPage
return m, keys.EmitKeySelected(&data[i])
}
}
}
// Navigate to the transaction page
return m, keys.EmitKeySelected(m.keysPage.SelectedKey())
if m.page == KeysPage {
selKey := m.keysPage.SelectedKey()
if selKey != nil {
m.page = TransactionPage
return m, keys.EmitKeySelected(selKey)
}
}
return m, nil
case "ctrl+c":
if m.page != GeneratePage {
return m, tea.Quit
Expand Down

0 comments on commit 3036470

Please sign in to comment.