add detail panel for selected human
This commit is contained in:
+113
-23
@@ -63,6 +63,29 @@ var (
|
||||
selectedTimestampStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#CCCCCC")).
|
||||
Italic(true)
|
||||
|
||||
detailPanelStyle = lipgloss.NewStyle().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("#7D56F4")).
|
||||
Padding(1, 2)
|
||||
|
||||
detailTitleStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#7D56F4")).
|
||||
Bold(true).
|
||||
Underline(true)
|
||||
|
||||
detailLabelStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#888888")).
|
||||
Bold(true)
|
||||
|
||||
detailValueStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#FAFAFA"))
|
||||
|
||||
codeBlockStyle = lipgloss.NewStyle().
|
||||
Background(lipgloss.Color("#1a1a1a")).
|
||||
Foreground(lipgloss.Color("#00FF00")).
|
||||
Padding(1, 1).
|
||||
MarginTop(1)
|
||||
)
|
||||
|
||||
type HumanData struct {
|
||||
@@ -77,8 +100,12 @@ type model struct {
|
||||
cursor int
|
||||
selectedHumanEmail string
|
||||
viewport viewport.Model
|
||||
detailViewport viewport.Model
|
||||
ready bool
|
||||
showKeypad bool
|
||||
terminalWidth int
|
||||
terminalHeight int
|
||||
focusOnDetail bool // true = detail panel has focus, false = list has focus
|
||||
}
|
||||
|
||||
func initialModel(humans []*HumanData) model {
|
||||
@@ -95,25 +122,35 @@ func (m model) Init() tea.Cmd {
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var (
|
||||
cmd tea.Cmd
|
||||
cmds []tea.Cmd
|
||||
)
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.terminalWidth = msg.Width
|
||||
m.terminalHeight = msg.Height
|
||||
|
||||
headerHeight := 3 // title + status + separator
|
||||
footerHeight := 2 // help text
|
||||
verticalMarginHeight := headerHeight + footerHeight
|
||||
|
||||
// Split width: 50% for list, 50% for details
|
||||
listWidth := msg.Width / 2
|
||||
detailWidth := msg.Width - listWidth - 3 // -3 for padding/border
|
||||
|
||||
if !m.ready {
|
||||
m.viewport = viewport.New(msg.Width, msg.Height-verticalMarginHeight)
|
||||
m.viewport = viewport.New(listWidth, msg.Height-verticalMarginHeight)
|
||||
m.viewport.YPosition = headerHeight
|
||||
m.viewport.SetContent(m.renderList())
|
||||
|
||||
m.detailViewport = viewport.New(detailWidth, msg.Height-verticalMarginHeight)
|
||||
m.detailViewport.YPosition = headerHeight
|
||||
m.detailViewport.SetContent(m.renderDetails())
|
||||
|
||||
m.ready = true
|
||||
} else {
|
||||
m.viewport.Width = msg.Width
|
||||
m.viewport.Width = listWidth
|
||||
m.viewport.Height = msg.Height - verticalMarginHeight
|
||||
|
||||
m.detailViewport.Width = detailWidth
|
||||
m.detailViewport.Height = msg.Height - verticalMarginHeight
|
||||
}
|
||||
|
||||
case tea.KeyMsg:
|
||||
@@ -121,27 +158,35 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case "ctrl+c", "q":
|
||||
return m, tea.Quit
|
||||
|
||||
case "ctrl+u":
|
||||
// Scroll detail panel up only
|
||||
m.detailViewport.LineUp(3)
|
||||
return m, nil
|
||||
|
||||
case "ctrl+d":
|
||||
// Scroll detail panel down only
|
||||
m.detailViewport.LineDown(3)
|
||||
return m, nil
|
||||
|
||||
case "up", "k":
|
||||
if m.cursor > 0 {
|
||||
m.cursor--
|
||||
m.viewport.SetContent(m.renderList())
|
||||
m.detailViewport.SetContent(m.renderDetails())
|
||||
m.detailViewport.GotoTop() // Reset detail scroll position
|
||||
}
|
||||
|
||||
case "down", "j":
|
||||
if m.cursor < len(m.humans)-1 {
|
||||
m.cursor++
|
||||
m.viewport.SetContent(m.renderList())
|
||||
m.detailViewport.SetContent(m.renderDetails())
|
||||
m.detailViewport.GotoTop() // Reset detail scroll position
|
||||
}
|
||||
case "t":
|
||||
m.showKeypad = !m.showKeypad
|
||||
m.viewport.SetContent(m.renderList())
|
||||
}
|
||||
}
|
||||
|
||||
m.viewport, cmd = m.viewport.Update(msg)
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m model) renderList() string {
|
||||
@@ -156,22 +201,58 @@ func (m model) renderList() string {
|
||||
// Current selection - highlighted
|
||||
cursor := cursorStyle.Render("▸")
|
||||
item := selectedItemStyle.Render(email)
|
||||
timestamp := selectedTimestampStyle.Render(fmt.Sprintf("joined %s", timeAgo))
|
||||
timestamp := selectedTimestampStyle.Render(fmt.Sprintf("(%s)", timeAgo))
|
||||
b.WriteString(fmt.Sprintf("%s %s %s\n", cursor, item, timestamp))
|
||||
if m.showKeypad {
|
||||
b.WriteString(fmt.Sprintf("%s\n", selectedItemStyle.Render(human.KeypadConfiguration)))
|
||||
}
|
||||
} else {
|
||||
// Normal item
|
||||
cursor := dimItemStyle.Render(" ")
|
||||
item := normalItemStyle.Render(email)
|
||||
timestamp := timestampStyle.Render(fmt.Sprintf("joined %s", timeAgo))
|
||||
timestamp := timestampStyle.Render(fmt.Sprintf("(%s)", timeAgo))
|
||||
b.WriteString(fmt.Sprintf("%s %s %s\n", cursor, item, timestamp))
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (m model) renderDetails() string {
|
||||
if len(m.humans) == 0 || m.cursor >= len(m.humans) {
|
||||
return ""
|
||||
}
|
||||
|
||||
human := m.humans[m.cursor]
|
||||
var b strings.Builder
|
||||
|
||||
// Title
|
||||
b.WriteString(detailTitleStyle.Render("Human Details") + "\n\n")
|
||||
|
||||
// Email
|
||||
b.WriteString(detailLabelStyle.Render("Email: "))
|
||||
b.WriteString(detailValueStyle.Render(human.Email) + "\n\n")
|
||||
|
||||
// Display Name
|
||||
if human.DisplayName != "" {
|
||||
b.WriteString(detailLabelStyle.Render("Display Name: "))
|
||||
b.WriteString(detailValueStyle.Render(human.DisplayName) + "\n\n")
|
||||
}
|
||||
|
||||
// Joined At
|
||||
b.WriteString(detailLabelStyle.Render("Joined: "))
|
||||
timeAgo := timediff.TimeDiff(human.JoinedAt)
|
||||
joinedFormatted := human.JoinedAt.Format("Jan 2, 2006 at 3:04 PM")
|
||||
b.WriteString(detailValueStyle.Render(fmt.Sprintf("%s (%s)", joinedFormatted, timeAgo)) + "\n\n")
|
||||
|
||||
// Keypad Configuration
|
||||
if human.KeypadConfiguration != "" {
|
||||
b.WriteString(detailLabelStyle.Render("Keypad Configuration:") + "\n")
|
||||
b.WriteString(codeBlockStyle.Render(human.KeypadConfiguration))
|
||||
} else {
|
||||
b.WriteString(detailLabelStyle.Render("Keypad Configuration: "))
|
||||
b.WriteString(dimItemStyle.Render("(none)"))
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (m model) View() string {
|
||||
if !m.ready {
|
||||
return "\n Initializing..."
|
||||
@@ -191,12 +272,21 @@ func (m model) View() string {
|
||||
statusBar := statusStyle.Render(fmt.Sprintf(" %d/%d", m.cursor+1, len(m.humans)))
|
||||
|
||||
// Help text
|
||||
help := helpStyle.Render("↑/k up • ↓/j down • t toggle keypads • q quit")
|
||||
help := helpStyle.Render("↑/k up • ↓/j down • ctrl+u/d scroll detail • q quit")
|
||||
|
||||
// Separator
|
||||
separator := borderStyle.Render(strings.Repeat("─", m.viewport.Width))
|
||||
// Create two-column layout
|
||||
leftPanel := m.viewport.View()
|
||||
rightPanel := m.detailViewport.View()
|
||||
|
||||
return fmt.Sprintf("%s%s\n%s\n%s\n%s", header, statusBar, separator, m.viewport.View(), help)
|
||||
// Combine panels side by side
|
||||
mainContent := lipgloss.JoinHorizontal(
|
||||
lipgloss.Top,
|
||||
leftPanel,
|
||||
" ", // spacing
|
||||
rightPanel,
|
||||
)
|
||||
|
||||
return fmt.Sprintf("%s%s\n%s\n%s", header, statusBar, mainContent, help)
|
||||
}
|
||||
|
||||
func unaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||
|
||||
Reference in New Issue
Block a user