WIP: UI improvements and code refactoring #2

Closed
dananglin wants to merge 25 commits from ui-overhall into main
6 changed files with 272 additions and 22 deletions
Showing only changes of commit eac8b17845 - Show all commits

83
card.go Normal file
View file

@ -0,0 +1,83 @@
package main
import (
"errors"
bolt "go.etcd.io/bbolt"
)
// Card represents a card on a Kanban board.
type Card struct {
ID int
Title string
Content string
}
// TODO: this function needs to be unit tested and documented.
func createCard(db *bolt.DB, title, content string) error {
// first ensure that there is a status list in the database
statusList, err := loadAllStatuses(db)
if err != nil {
return err
}
if len(statusList) == 0 {
return errors.New("the status list cannot be empty")
}
card := Card{
ID: -1,
Title: title,
Content: content,
}
cardID, err := saveCard(db, card)
if err != nil {
return err
}
cardIDs := statusList[0].CardIds
cardIDs = append(cardIDs, cardID)
statusList[0].CardIds = cardIDs
// TODO: change the below to save a single status
if err := saveStatuses(db, statusList); err != nil {
return err
}
return nil
}
// readCard returns a Card value from the database.
func readCard(db *bolt.DB, id int) (Card, error) {
return loadCard(db, id)
}
// TODO: unit test and document this function.
func updateCard(db *bolt.DB, id int, title, content string) error {
card, err := loadCard(db, id)
if err != nil {
return nil
}
card.Title = title
card.Content = content
if _, err := saveCard(db, card); err != nil {
return err
}
return nil
}
// TODO: finish implementation
func moveCard(db *bolt.DB, fromStatusID, toStatusID int) error {
return nil
}
// TODO: finish implementation
func deleteCard(db *bolt.DB, id int) error {
return nil
}

1
go.mod
View file

@ -3,6 +3,7 @@ module forge.dananglin.me.uk/code/dananglin/pelican
go 1.16 go 1.16
require ( require (
github.com/eiannone/keyboard v0.0.0-20200508000154-caf4b762e807
github.com/magefile/mage v1.11.0 github.com/magefile/mage v1.11.0
go.etcd.io/bbolt v1.3.6 go.etcd.io/bbolt v1.3.6
) )

2
go.sum
View file

@ -1,3 +1,5 @@
github.com/eiannone/keyboard v0.0.0-20200508000154-caf4b762e807 h1:jdjd5e68T4R/j4PWxfZqcKY8KtT9oo8IPNVuV4bSXDQ=
github.com/eiannone/keyboard v0.0.0-20200508000154-caf4b762e807/go.mod h1:Xoiu5VdKMvbRgHuY7+z64lhu/7lvax/22nzASF6GrO8=
github.com/magefile/mage v1.11.0 h1:C/55Ywp9BpgVVclD3lRnSYCwXTYxmSppIgLeDYlNuls= github.com/magefile/mage v1.11.0 h1:C/55Ywp9BpgVVclD3lRnSYCwXTYxmSppIgLeDYlNuls=
github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=

49
main.go
View file

@ -1,4 +1,53 @@
package main package main
import (
"fmt"
"os"
)
func main() { func main() {
path, err := dbPath("")
if err != nil {
fmt.Printf("Error: Unable to get the database path, %s", err)
os.Exit(1)
}
db, err := openDatabase(path)
if err != nil {
fmt.Printf("Error: Unable to open the database, %s", err)
os.Exit(1)
}
defer db.Close()
if err := ensureBuckets(db); err != nil {
fmt.Printf("Error: Unable to ensure buckets exist, %s", err)
os.Exit(1)
}
var statusList []Status
statusList, err = readStatuses(db)
if err != nil {
fmt.Printf("Error: Unable to get status list, %s", err)
os.Exit(1)
}
if len(statusList) == 0 {
newStatusList := newDefaultStatusList()
if err := saveStatuses(db, newStatusList); err != nil {
fmt.Printf("Error: Unable to save the default status list to the database, %s", err)
os.Exit(1)
}
_, err = readStatuses(db)
if err != nil {
fmt.Printf("Error: Unable to get status list, %s", err)
os.Exit(1)
}
}
if err := badUI(db); err != nil {
fmt.Printf("Error: %s", err)
}
} }

View file

@ -7,13 +7,6 @@ import (
bolt "go.etcd.io/bbolt" bolt "go.etcd.io/bbolt"
) )
// Card represents a card on a Kanban board.
type Card struct {
ID int
Title string
Content string
}
// Status represents the status of the Kanban board. // Status represents the status of the Kanban board.
type Status struct { type Status struct {
ID int ID int
@ -37,9 +30,7 @@ func (s ByStatusOrder) Less(i, j int) bool {
return s[i].Order < s[j].Order return s[i].Order < s[j].Order
} }
// readStatuses reads all statuses from the database. If there are no statuses in the database then the default // readStatuses returns an ordered list of statuses from the database.
// status list is created and stored in the database. Before returning the status list, the list is ordered based
// on the status' 'Order' field.
// TODO: function needs to be unit tested. // TODO: function needs to be unit tested.
func readStatuses(db *bolt.DB) ([]Status, error) { func readStatuses(db *bolt.DB) ([]Status, error) {
statuses, err := loadAllStatuses(db) statuses, err := loadAllStatuses(db)
@ -47,34 +38,45 @@ func readStatuses(db *bolt.DB) ([]Status, error) {
return statuses, fmt.Errorf("unable to read statuses, %w", err) return statuses, fmt.Errorf("unable to read statuses, %w", err)
} }
if len(statuses) == 0 {
if err := createDefaultStatusList(db); err != nil {
return statuses, fmt.Errorf("unable to create the default statuses, %w", err)
}
readStatuses(db)
}
sort.Sort(ByStatusOrder(statuses)) sort.Sort(ByStatusOrder(statuses))
return statuses, nil return statuses, nil
} }
// createDefaultStatusList creates the default list of statuses and saves the statuses to the database. func readStatus(db *bolt.DB) (Status, error) {
func createDefaultStatusList(db *bolt.DB) error { return Status{}, nil
s := []Status{ }
func createStatus(db *bolt.DB) error {
return nil
}
func updateStatus(db *bolt.DB) error {
return nil
}
func deleteStatus(db *bolt.DB) error {
return nil
}
// newDefaultStatusList creates the default list of statuses and saves the statuses to the database.
// TODO: function needs to be unit tested.
func newDefaultStatusList() []Status {
return []Status{
{ {
ID: -1,
Name: "To Do", Name: "To Do",
Order: 1, Order: 1,
}, },
{ {
ID: -1,
Name: "Doing", Name: "Doing",
Order: 2, Order: 2,
}, },
{ {
ID: -1,
Name: "Done", Name: "Done",
Order: 3, Order: 3,
}, },
} }
return saveStatuses(db, s)
} }

113
ui.go Normal file
View file

@ -0,0 +1,113 @@
// Right now this will be a very simple, scuffed interface.
package main
import (
"fmt"
bolt "go.etcd.io/bbolt"
"github.com/eiannone/keyboard"
)
func badUI(db *bolt.DB) error {
keysEvents, err := keyboard.GetKeys(10)
if err != nil {
return fmt.Errorf("unable to create the keysEvent channel, %w", err)
}
defer func() {
_ = keyboard.Close()
}()
// infinite loop
fmt.Println(uiUsage())
app:
for {
event := <-keysEvents
if event.Err != nil {
return fmt.Errorf("keys event error: %w", event.Err)
}
switch event.Rune {
case 'q':
break app
case 'r':
if err := uiRefreshScuffedBoard(db); err != nil {
fmt.Printf("Error: Unable to refresh board, %s\n", err)
}
case 'a':
if err := uiCreateCard(db); err != nil {
fmt.Printf("Error: Unable to add a card, %s\n", err)
}
case 'v':
if err := uiViewCard(db, 1); err != nil {
fmt.Printf("Error: Unable to view card, %s\n", err)
}
default:
fmt.Println("Error: Unknown key event.")
}
}
// wait for a keyboard press
// take action
// a to add a card
// u to update a card
return nil
}
func uiRefreshScuffedBoard(db *bolt.DB) error {
statusList, err := readStatuses(db)
if err != nil {
return err
}
fmt.Printf("--------------------\n")
for _, s := range statusList{
fmt.Printf("Status ID: %d\nStatus Name: \"%s\"\nCard IDs: %v\n\n", s.ID, s.Name, s.CardIds)
}
fmt.Printf("--------------------\n\n\n")
return nil
}
func uiCreateCard(db *bolt.DB) error {
title := "A card title"
content := "As a user, this is a ticket for me.\nAs a user, I want to close it."
if err := createCard(db, title, content); err != nil {
return err
}
fmt.Println("Sample card created successfully.")
return nil
}
func uiViewCard(db *bolt.DB, id int) error {
card, err := readCard(db, id)
if err != nil {
return err
}
fmt.Printf("====================\n")
fmt.Printf("[%d] %s\n", card.ID, card.Title)
fmt.Printf("--------------------\n")
fmt.Println(card.Content)
fmt.Printf("====================\n")
return nil
}
func uiUsage() string {
usage := `
Press 'q' to quit
Press 'r' to refresh the board
Press 'a' to add a sample card
Press 'v' to view the first sample card
`
return usage
}