feat(ui): add support for viewing the details of a card #17

Manually merged
dananglin merged 1 commit from 16-add-support-for-displaying-a-card into main 2024-01-12 15:11:53 +00:00
4 changed files with 134 additions and 24 deletions

View file

@ -81,10 +81,13 @@ and initialises the database with an empty project.
|Create a new card
|CTRL + d
|Delete card
|Delete a card
|m
|Move card between statuses
|Move a card from one column to another (press `Enter` to confirm your selection).
|Enter
|View the details of a card (press `ESC` to go back to the main board).
|===
== Inspiration

69
internal/ui/cardview.go Normal file
View file

@ -0,0 +1,69 @@
package ui
import (
"fmt"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
type cardView struct {
*tview.TextView
frame *tview.Frame
contentFormat string
}
func newCardView() *cardView {
border := tcell.ColorOrangeRed.TrueColor()
background := tcell.ColorBlack.TrueColor()
content := tview.NewTextView()
// Stylise the TextView
content.SetDynamicColors(true).
SetBorder(true).
SetBorderPadding(0, 0, 1, 1).
SetBorderColor(border).
SetBackgroundColor(background)
cardContentFormat := `[green::b][#%d[] %s[-:-:-:-]
[green::b]Status:[white::-] %s [green::b]Created:[white::-] %s
[green::b]Description:[white::-]
%s
`
view := cardView{
TextView: content,
frame: tview.NewFrame(content),
contentFormat: cardContentFormat,
}
return &view
}
func (c *cardView) setDoneFunc(handler func(key tcell.Key)) {
c.SetDoneFunc(handler)
}
func (c *cardView) print(id int, title, status, created, description string) {
fmt.Fprintf(c, c.contentFormat, id, title, status, created, description)
}
func (c *cardView) Draw(screen tcell.Screen) {
height := 25
width := 50
screenWidth, screenHeight := screen.Size()
// Set the form's position and size.
x := (screenWidth - width) / 2
y := (screenHeight - height) / 2
c.SetRect(x, y, width, height)
// Draw the frame.
c.frame.SetRect(x, y, width, height)
c.frame.Draw(screen)
}

View file

@ -19,6 +19,7 @@ type column struct {
*tview.Box
statusID int
statusName string
cards []card
focusedCard int
getBoardModeFunc func() boardMode
@ -28,6 +29,7 @@ func newColumn(statusID int, statusName string, getBoardModeFunc func() boardMod
column := column{
Box: tview.NewBox(),
statusID: statusID,
statusName: statusName,
cards: nil,
focusedCard: 0,
getBoardModeFunc: getBoardModeFunc,

View file

@ -28,6 +28,7 @@ const (
quitPage string = "quit"
cardFormPage string = "card form"
deleteCardModalPage string = "delete card modal"
viewPage string = "view"
)
type UI struct {
@ -43,6 +44,7 @@ type UI struct {
cardForm *cardForm
deleteCardModal *tview.Modal
statusSelection statusSelection
view *cardView
}
// NewUI returns a new UI value.
@ -64,6 +66,7 @@ func NewUI(path string) (UI, error) {
deleteCardModal: tview.NewModal(),
mode: normal,
statusSelection: statusSelection{0, 0, 0},
view: newCardView(),
}
if err := userInterface.init(); err != nil {
@ -85,7 +88,33 @@ func (u *UI) init() error {
return fmt.Errorf("error initialising the status columns; %w", err)
}
u.flex.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
u.flex.SetInputCapture(u.inputCapture())
u.pages.AddPage(mainPage, u.flex, true, true)
u.initQuitModal()
u.pages.AddPage(quitPage, u.quitModal, false, false)
u.initCardForm()
u.pages.AddPage(cardFormPage, u.cardForm, false, false)
u.initDeleteCardModal()
u.pages.AddPage(deleteCardModalPage, u.deleteCardModal, false, false)
u.initView()
u.pages.AddPage(viewPage, u.view, false, false)
u.SetRoot(u.pages, true)
if err := u.refresh(false); err != nil {
return fmt.Errorf("error refreshing the board; %w", err)
}
return nil
}
func (u *UI) inputCapture() func(event *tcell.EventKey) *tcell.EventKey {
return func(event *tcell.EventKey) *tcell.EventKey {
key, letter := event.Key(), event.Rune()
switch {
@ -131,11 +160,19 @@ func (u *UI) init() error {
u.mode = normal
}
case key == tcell.KeyEnter:
if u.mode == selection {
switch u.mode {
case normal:
card, _ := u.getFocusedCard()
status := u.focusedStatusName()
u.view.print(card.ID, card.Title, status, card.Created, card.Description)
u.pages.ShowPage(viewPage)
u.SetFocus(u.view)
case selection:
u.statusSelection.nextStatusID = u.focusedStatusID()
if u.statusSelection.currentStatusID != u.statusSelection.nextStatusID {
u.statusSelection.moveCard(u.board)
}
u.statusSelection = statusSelection{0, 0, 0}
u.mode = normal
_ = u.refresh(false)
@ -143,26 +180,7 @@ func (u *UI) init() error {
}
return event
})
u.pages.AddPage(mainPage, u.flex, true, true)
u.initQuitModal()
u.pages.AddPage(quitPage, u.quitModal, false, false)
u.initCardForm()
u.pages.AddPage(cardFormPage, u.cardForm, false, false)
u.initDeleteCardModal()
u.pages.AddPage(deleteCardModalPage, u.deleteCardModal, false, false)
u.SetRoot(u.pages, true)
if err := u.refresh(false); err != nil {
return fmt.Errorf("error refreshing the board; %w", err)
}
return nil
}
// initCardForm initialises the card form.
@ -218,6 +236,19 @@ func (u *UI) initQuitModal() {
SetDoneFunc(doneFunc)
}
// initView initialises the view window for displaying the card.
func (u *UI) initView() {
doneFunc := func(key tcell.Key) {
if key == tcell.KeyEsc {
u.pages.HidePage(viewPage)
u.view.Clear()
u.setColumnFocus()
}
}
u.view.setDoneFunc(doneFunc)
}
// newCard creates and saves a new card to the database.
func (u *UI) newCard(title, description string) error {
args := board.CardArgs{
@ -376,7 +407,12 @@ func (u *UI) focusedCardID() int {
return id
}
// focusedStatusID returns the ID of thestatus (column) in focus.
// focusedStatusID returns the ID of the status (column) in focus.
func (u *UI) focusedStatusID() int {
return u.columns[u.focusedColumn].statusID
}
// focusedStatusName returns the name of the status (column) in focus.
func (u *UI) focusedStatusName() string {
return u.columns[u.focusedColumn].statusName
}