feat(ui): add support for viewing the details of a card #17
4 changed files with 134 additions and 24 deletions
|
@ -81,10 +81,13 @@ and initialises the database with an empty project.
|
||||||
|Create a new card
|
|Create a new card
|
||||||
|
|
||||||
|CTRL + d
|
|CTRL + d
|
||||||
|Delete card
|
|Delete a card
|
||||||
|
|
||||||
|m
|
|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
|
== Inspiration
|
||||||
|
|
69
internal/ui/cardview.go
Normal file
69
internal/ui/cardview.go
Normal 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)
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ type column struct {
|
||||||
*tview.Box
|
*tview.Box
|
||||||
|
|
||||||
statusID int
|
statusID int
|
||||||
|
statusName string
|
||||||
cards []card
|
cards []card
|
||||||
focusedCard int
|
focusedCard int
|
||||||
getBoardModeFunc func() boardMode
|
getBoardModeFunc func() boardMode
|
||||||
|
@ -28,6 +29,7 @@ func newColumn(statusID int, statusName string, getBoardModeFunc func() boardMod
|
||||||
column := column{
|
column := column{
|
||||||
Box: tview.NewBox(),
|
Box: tview.NewBox(),
|
||||||
statusID: statusID,
|
statusID: statusID,
|
||||||
|
statusName: statusName,
|
||||||
cards: nil,
|
cards: nil,
|
||||||
focusedCard: 0,
|
focusedCard: 0,
|
||||||
getBoardModeFunc: getBoardModeFunc,
|
getBoardModeFunc: getBoardModeFunc,
|
||||||
|
|
|
@ -28,6 +28,7 @@ const (
|
||||||
quitPage string = "quit"
|
quitPage string = "quit"
|
||||||
cardFormPage string = "card form"
|
cardFormPage string = "card form"
|
||||||
deleteCardModalPage string = "delete card modal"
|
deleteCardModalPage string = "delete card modal"
|
||||||
|
viewPage string = "view"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UI struct {
|
type UI struct {
|
||||||
|
@ -43,6 +44,7 @@ type UI struct {
|
||||||
cardForm *cardForm
|
cardForm *cardForm
|
||||||
deleteCardModal *tview.Modal
|
deleteCardModal *tview.Modal
|
||||||
statusSelection statusSelection
|
statusSelection statusSelection
|
||||||
|
view *cardView
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUI returns a new UI value.
|
// NewUI returns a new UI value.
|
||||||
|
@ -64,6 +66,7 @@ func NewUI(path string) (UI, error) {
|
||||||
deleteCardModal: tview.NewModal(),
|
deleteCardModal: tview.NewModal(),
|
||||||
mode: normal,
|
mode: normal,
|
||||||
statusSelection: statusSelection{0, 0, 0},
|
statusSelection: statusSelection{0, 0, 0},
|
||||||
|
view: newCardView(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := userInterface.init(); err != nil {
|
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)
|
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()
|
key, letter := event.Key(), event.Rune()
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
@ -131,11 +160,19 @@ func (u *UI) init() error {
|
||||||
u.mode = normal
|
u.mode = normal
|
||||||
}
|
}
|
||||||
case key == tcell.KeyEnter:
|
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()
|
u.statusSelection.nextStatusID = u.focusedStatusID()
|
||||||
if u.statusSelection.currentStatusID != u.statusSelection.nextStatusID {
|
if u.statusSelection.currentStatusID != u.statusSelection.nextStatusID {
|
||||||
u.statusSelection.moveCard(u.board)
|
u.statusSelection.moveCard(u.board)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.statusSelection = statusSelection{0, 0, 0}
|
u.statusSelection = statusSelection{0, 0, 0}
|
||||||
u.mode = normal
|
u.mode = normal
|
||||||
_ = u.refresh(false)
|
_ = u.refresh(false)
|
||||||
|
@ -143,26 +180,7 @@ func (u *UI) init() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
return event
|
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.
|
// initCardForm initialises the card form.
|
||||||
|
@ -218,6 +236,19 @@ func (u *UI) initQuitModal() {
|
||||||
SetDoneFunc(doneFunc)
|
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.
|
// newCard creates and saves a new card to the database.
|
||||||
func (u *UI) newCard(title, description string) error {
|
func (u *UI) newCard(title, description string) error {
|
||||||
args := board.CardArgs{
|
args := board.CardArgs{
|
||||||
|
@ -376,7 +407,12 @@ func (u *UI) focusedCardID() int {
|
||||||
return id
|
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 {
|
func (u *UI) focusedStatusID() int {
|
||||||
return u.columns[u.focusedColumn].statusID
|
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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue