Refactor UI layout and add dashboard, logs, sidebar

Replaces the infobar with a new dashboard component, introduces a sidebar for navigation, and adds a logs view for runtime messages. Refactors the main app layout to use a sidebar and content pages, updates menu handling, and improves modularity of UI components. Removes infobar and related code, and adds new files for dashboard, layout, logs, and sidebar.
This commit is contained in:
lx1056758714-glitch
2025-12-19 15:39:25 +08:00
parent a5dc514fcb
commit d18e413df0
6 changed files with 418 additions and 568 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,59 @@
package dashboard
import (
"fmt"
"github.com/rivo/tview"
"github.com/sjzar/chatlog/internal/ui/style"
)
type Dashboard struct {
*tview.Table
}
func New() *Dashboard {
d := &Dashboard{
Table: tview.NewTable(),
}
d.SetBorders(false)
d.SetBorder(true)
d.SetTitle(" 状态概览 ")
d.SetBorderColor(style.BorderColor)
d.SetBackgroundColor(style.BgColor)
return d
}
func (d *Dashboard) Update(data map[string]string) {
d.Clear()
row := 0
headerColor := style.InfoBarItemFgColor
keys := []string{
"Account", "PID", "Status", "ExePath",
"Platform", "Version", "Session", "Data Key",
"Image Key", "Data Usage", "Data Dir",
"Work Usage", "Work Dir", "HTTP Server", "Auto Decrypt",
}
for _, key := range keys {
val, ok := data[key]
if !ok {
continue
}
d.SetCell(row, 0, tview.NewTableCell(fmt.Sprintf(" [%s::b]%s", headerColor, key)).
SetAlign(tview.AlignRight).
SetExpansion(1).
SetTextColor(style.FgColor))
d.SetCell(row, 1, tview.NewTableCell(fmt.Sprintf(" %s", val)).
SetAlign(tview.AlignLeft).
SetExpansion(3).
SetTextColor(style.FgColor))
row++
}
}

View File

@@ -1,230 +0,0 @@
package infobar
import (
"fmt"
"github.com/sjzar/chatlog/internal/ui/style"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
const (
Title = "infobar"
)
// InfoBarViewHeight info bar height.
const (
InfoBarViewHeight = 8
accountRow = 0
statusRow = 1
platformRow = 2
sessionRow = 3
dataUsageRow = 4
workUsageRow = 5
httpServerRow = 6
autoDecryptRow = 7
// 列索引
labelCol1 = 0 // 第一列标签
valueCol1 = 1 // 第一列值
labelCol2 = 2 // 第二列标签
valueCol2 = 3 // 第二列值
totalCols = 4
)
// InfoBar implements the info bar primitive.
type InfoBar struct {
*tview.Box
title string
table *tview.Table
}
// NewInfoBar returns info bar view.
func New() *InfoBar {
table := tview.NewTable()
headerColor := style.InfoBarItemFgColor
// Account 和 PID 行
table.SetCell(
accountRow,
labelCol1,
tview.NewTableCell(fmt.Sprintf(" [%s::]%s", headerColor, "Account:")),
)
table.SetCell(accountRow, valueCol1, tview.NewTableCell(""))
table.SetCell(
accountRow,
labelCol2,
tview.NewTableCell(fmt.Sprintf(" [%s::]%s", headerColor, "PID:")),
)
table.SetCell(accountRow, valueCol2, tview.NewTableCell(""))
// Status 和 ExePath 行
table.SetCell(
statusRow,
labelCol1,
tview.NewTableCell(fmt.Sprintf(" [%s::]%s", headerColor, "Status:")),
)
table.SetCell(statusRow, valueCol1, tview.NewTableCell(""))
table.SetCell(
statusRow,
labelCol2,
tview.NewTableCell(fmt.Sprintf(" [%s::]%s", headerColor, "ExePath:")),
)
table.SetCell(statusRow, valueCol2, tview.NewTableCell(""))
// Platform 和 Version 行
table.SetCell(
platformRow,
labelCol1,
tview.NewTableCell(fmt.Sprintf(" [%s::]%s", headerColor, "Platform:")),
)
table.SetCell(platformRow, valueCol1, tview.NewTableCell(""))
table.SetCell(
platformRow,
labelCol2,
tview.NewTableCell(fmt.Sprintf(" [%s::]%s", headerColor, "Version:")),
)
table.SetCell(platformRow, valueCol2, tview.NewTableCell(""))
// Session 和 Data Key 行
table.SetCell(
sessionRow,
labelCol1,
tview.NewTableCell(fmt.Sprintf(" [%s::]%s", headerColor, "Session:")),
)
table.SetCell(sessionRow, valueCol1, tview.NewTableCell(""))
table.SetCell(
sessionRow,
labelCol2,
tview.NewTableCell(fmt.Sprintf(" [%s::]%s", headerColor, "Data Key:")),
)
table.SetCell(sessionRow, valueCol2, tview.NewTableCell(""))
// Data Usage 和 Data Dir 行
table.SetCell(
dataUsageRow,
labelCol1,
tview.NewTableCell(fmt.Sprintf(" [%s::]%s", headerColor, "Data Usage:")),
)
table.SetCell(dataUsageRow, valueCol1, tview.NewTableCell(""))
table.SetCell(
dataUsageRow,
labelCol2,
tview.NewTableCell(fmt.Sprintf(" [%s::]%s", headerColor, "Data Dir:")),
)
table.SetCell(dataUsageRow, valueCol2, tview.NewTableCell(""))
// Work Usage 和 Work Dir 行
table.SetCell(
workUsageRow,
labelCol1,
tview.NewTableCell(fmt.Sprintf(" [%s::]%s", headerColor, "Work Usage:")),
)
table.SetCell(workUsageRow, valueCol1, tview.NewTableCell(""))
table.SetCell(
workUsageRow,
labelCol2,
tview.NewTableCell(fmt.Sprintf(" [%s::]%s", headerColor, "Work Dir:")),
)
table.SetCell(workUsageRow, valueCol2, tview.NewTableCell(""))
// HTTP Server 行
table.SetCell(
httpServerRow,
labelCol1,
tview.NewTableCell(fmt.Sprintf(" [%s::]%s", headerColor, "HTTP Server:")),
)
table.SetCell(httpServerRow, valueCol1, tview.NewTableCell(""))
table.SetCell(
httpServerRow,
labelCol2,
tview.NewTableCell(fmt.Sprintf(" [%s::]%s", headerColor, "Image Key:")),
)
table.SetCell(httpServerRow, valueCol2, tview.NewTableCell(""))
table.SetCell(
autoDecryptRow,
labelCol1,
tview.NewTableCell(fmt.Sprintf(" [%s::]%s", headerColor, "Auto Decrypt:")),
)
table.SetCell(autoDecryptRow, valueCol1, tview.NewTableCell(""))
// infobar
infoBar := &InfoBar{
Box: tview.NewBox(),
title: Title,
table: table,
}
return infoBar
}
func (info *InfoBar) UpdateAccount(account string) {
info.table.GetCell(accountRow, valueCol1).SetText(account)
}
func (info *InfoBar) UpdateBasicInfo(pid int, version string, exePath string) {
info.table.GetCell(accountRow, valueCol2).SetText(fmt.Sprintf("%d", pid))
info.table.GetCell(statusRow, valueCol2).SetText(exePath)
info.table.GetCell(platformRow, valueCol2).SetText(version)
}
func (info *InfoBar) UpdateStatus(status string) {
info.table.GetCell(statusRow, valueCol1).SetText(status)
}
func (info *InfoBar) UpdatePlatform(text string) {
info.table.GetCell(platformRow, valueCol1).SetText(text)
}
func (info *InfoBar) UpdateSession(text string) {
info.table.GetCell(sessionRow, valueCol1).SetText(text)
}
func (info *InfoBar) UpdateDataKey(key string) {
info.table.GetCell(sessionRow, valueCol2).SetText(key)
}
func (info *InfoBar) UpdateDataUsageDir(dataUsage string, dataDir string) {
info.table.GetCell(dataUsageRow, valueCol1).SetText(dataUsage)
info.table.GetCell(dataUsageRow, valueCol2).SetText(dataDir)
}
func (info *InfoBar) UpdateWorkUsageDir(workUsage string, workDir string) {
info.table.GetCell(workUsageRow, valueCol1).SetText(workUsage)
info.table.GetCell(workUsageRow, valueCol2).SetText(workDir)
}
// UpdateHTTPServer updates HTTP Server value.
func (info *InfoBar) UpdateHTTPServer(server string) {
info.table.GetCell(httpServerRow, valueCol1).SetText(server)
}
func (info *InfoBar) UpdateImageKey(key string) {
info.table.GetCell(httpServerRow, valueCol2).SetText(key)
}
// UpdateAutoDecrypt updates Auto Decrypt value.
func (info *InfoBar) UpdateAutoDecrypt(text string) {
info.table.GetCell(autoDecryptRow, valueCol1).SetText(text)
}
// Draw draws this primitive onto the screen.
func (info *InfoBar) Draw(screen tcell.Screen) {
info.Box.DrawForSubclass(screen, info)
info.Box.SetBorder(false)
x, y, width, height := info.GetInnerRect()
info.table.SetRect(x, y, width, height)
info.table.SetBorder(false)
info.table.Draw(screen)
}

View File

@@ -0,0 +1,29 @@
package layout
import (
"github.com/rivo/tview"
"github.com/sjzar/chatlog/internal/ui/sidebar"
)
type Layout struct {
*tview.Flex
Sidebar *sidebar.Sidebar
Pages *tview.Pages
}
func New(s *sidebar.Sidebar, pages *tview.Pages) *Layout {
l := &Layout{
Flex: tview.NewFlex(),
Sidebar: s,
Pages: pages,
}
l.AddItem(s, 20, 0, true).
AddItem(pages, 0, 1, false)
return l
}
func (l *Layout) FocusSidebar() {
l.AddItem(l.Sidebar, 20, 0, true)
}

43
internal/ui/logs/logs.go Normal file
View File

@@ -0,0 +1,43 @@
package logs
import (
"fmt"
"time"
"github.com/rivo/tview"
"github.com/sjzar/chatlog/internal/ui/style"
)
type Logs struct {
*tview.TextView
}
func New() *Logs {
l := &Logs{
TextView: tview.NewTextView(),
}
l.SetDynamicColors(true)
l.SetScrollable(true)
l.SetWrap(true)
l.SetBorder(true)
l.SetTitle(" 日志 ")
l.SetBorderColor(style.BorderColor)
l.SetBackgroundColor(style.BgColor)
l.SetTextColor(style.FgColor)
return l
}
func (l *Logs) AddLog(msg string) {
fmt.Fprintf(l, "[%s]%s[white] %s\n",
time.Now().Format("15:04:05"),
"",
msg)
l.ScrollToEnd()
}
func (l *Logs) Write(p []byte) (n int, err error) {
l.AddLog(string(p))
return len(p), nil
}

View File

@@ -0,0 +1,34 @@
package sidebar
import (
"github.com/rivo/tview"
"github.com/sjzar/chatlog/internal/ui/style"
)
type Sidebar struct {
*tview.List
}
func New(onSelected func(int, string, string, rune)) *Sidebar {
s := &Sidebar{
List: tview.NewList(),
}
s.ShowSecondaryText(false)
s.SetBackgroundColor(style.BgColor)
s.SetMainTextColor(style.FgColor)
s.SetSelectedBackgroundColor(style.MenuBgColor)
s.SetSelectedTextColor(style.PageHeaderFgColor)
s.SetBorder(true)
s.SetTitle(" 导航 ")
s.SetTitleAlign(tview.AlignLeft)
s.SetBorderColor(style.BorderColor)
s.SetSelectedFunc(onSelected)
return s
}
func (s *Sidebar) AddItem(text string, shortcut rune) {
s.List.AddItem(text, "", shortcut, nil)
}