Files
LazyPM/pkg/tui/views/kanban/view.go
Robin Olsen 3e9a903506 works now
2026-03-18 11:58:31 +01:00

143 lines
4.9 KiB
Go

package kanban
import (
"charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
"github.com/LazyBachelor/LazyPM/pkg/tui/components"
"github.com/LazyBachelor/LazyPM/pkg/tui/styles"
)
func (m *Model) View() tea.View {
if m.width == 0 || m.height == 0 {
// if there is no space just print a loading message
return tea.NewView("Loading...")
}
m.helpBar.SetWidth(m.width)
header := m.header.View(m.width)
headerHeight := m.header.Height()
footer := components.RenderFooter(m.width, &m.helpBar, m.currentFeedback)
footerHeight := lipgloss.Height(footer)
contentHeight := m.height - headerHeight - footerHeight
totalContentWidth := m.width - 1
colWidth := totalContentWidth / 4
if colWidth < 20 {
colWidth = 20
}
// Leave some space for the detail view below the board.
boardHeight := contentHeight / 2
if boardHeight < 5 {
boardHeight = contentHeight
}
m.todoList.SetSize(colWidth, boardHeight-1)
m.inProgList.SetSize(colWidth, boardHeight-1)
m.blockedList.SetSize(colWidth, boardHeight-1)
m.doneList.SetSize(colWidth, boardHeight-1)
// Only highlight the selected row in the focused column.
m.todoList.SetHighlightSelected(!m.focusOnDetail && m.focusedColumn == 0)
m.inProgList.SetHighlightSelected(!m.focusOnDetail && m.focusedColumn == 1)
m.blockedList.SetHighlightSelected(!m.focusOnDetail && m.focusedColumn == 2)
m.doneList.SetHighlightSelected(!m.focusOnDetail && m.focusedColumn == 3)
// Detail view takes full width below the board.
m.issueDetail.SetSize(totalContentWidth, contentHeight-boardHeight)
todoLabel := styles.LabelStyle.Render("To Do")
inProgLabel := styles.LabelStyle.Render("In Progress")
blockedLabel := styles.LabelStyle.Render("Blocked")
doneLabel := styles.LabelStyle.Render("Done")
highlight := lipgloss.NewStyle().Foreground(styles.Primary).Bold(true)
switch m.focusedColumn {
case 0:
todoLabel = highlight.Render("To Do ▶")
case 1:
inProgLabel = highlight.Render("In Progress ▶")
case 2:
blockedLabel = highlight.Render("Blocked ▶")
case 3:
doneLabel = highlight.Render("Done ▶")
}
todoCol := lipgloss.JoinVertical(lipgloss.Left, todoLabel, m.todoList.View())
inProgCol := lipgloss.JoinVertical(lipgloss.Left, inProgLabel, m.inProgList.View())
blockedCol := lipgloss.JoinVertical(lipgloss.Left, blockedLabel, m.blockedList.View())
doneCol := lipgloss.JoinVertical(lipgloss.Left, doneLabel, m.doneList.View())
board := lipgloss.JoinHorizontal(lipgloss.Left, todoCol, inProgCol, blockedCol, doneCol)
content := lipgloss.JoinVertical(lipgloss.Left, board, m.issueDetail.View())
// Add spacer to lock footer to bottom of screen when content is shorter than available space
// This is to avoid having the footer floating above the bottom of the screen
mainView := lipgloss.JoinVertical(lipgloss.Left, header, content, footer)
mainViewHeight := lipgloss.Height(mainView)
if mainViewHeight < m.height {
spacerHeight := m.height - mainViewHeight
spacer := lipgloss.NewStyle().Height(spacerHeight).Width(m.width).Render("")
mainView = lipgloss.JoinVertical(lipgloss.Left, header, content, spacer, footer)
}
if m.choosingCloseReason {
reasonContent := lipgloss.JoinVertical(lipgloss.Left,
styles.LabelStyle.Render("Choose closing reason for "+m.closeReasonIssueID+":"),
lipgloss.NewStyle().Foreground(styles.FaintText).Render("d = Done u = Duplicate issue w = Won't fix o = Obsolete h = Other"),
lipgloss.NewStyle().Foreground(styles.FaintText).Render("Esc = cancel"),
)
reasonBoxWidth := min(70, m.width-4)
reasonBox := styles.ContainerStyle.
Width(reasonBoxWidth).
BorderForeground(styles.PrimaryBorder).
Render(reasonContent)
return tea.NewView(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, reasonBox))
}
if m.closingOtherReason {
editBoxWidth := min(60, m.width-4)
m.closeReasonInput.SetWidth(editBoxWidth - 2)
m.closeReasonInput.SetHeight(4)
editContent := lipgloss.JoinVertical(lipgloss.Left,
styles.LabelStyle.Render("Enter closing reason for "+m.closeReasonIssueID+" (Enter or Ctrl+S to save, Esc to cancel):"),
m.closeReasonInput.View(),
)
editBox := styles.ContainerStyle.
Width(editBoxWidth).
BorderForeground(styles.PrimaryBorder).
Render(editContent)
return tea.NewView(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, editBox))
}
return tea.NewView(components.RenderModals(
m.width,
m.height,
m.editingTitle,
m.titleInput.View(),
m.editingDescription,
m.descriptionInput.View(),
m.creatingIssue,
m.createTitleInput.View(),
m.confirmingDelete,
m.deleteConfirmID,
m.choosingStatus,
m.statusIssueID,
m.choosingPriority,
m.priorityIssueID,
m.choosingType,
m.typeIssueID,
m.editingAssignee,
m.assigneeInput.View(),
mainView,
))
}
func (m *Model) footer() string {
// Kept for backwards compatibility; delegate to the shared helper.
return components.RenderFooter(m.width, &m.helpBar, m.currentFeedback)
}