x
This commit is contained in:
68
internal/ui/footer/footer.go
Normal file
68
internal/ui/footer/footer.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package footer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sjzar/chatlog/internal/ui/style"
|
||||
"github.com/sjzar/chatlog/pkg/version"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
const (
|
||||
Title = "footer"
|
||||
)
|
||||
|
||||
type Footer struct {
|
||||
*tview.Flex
|
||||
title string
|
||||
copyRight *tview.TextView
|
||||
help *tview.TextView
|
||||
}
|
||||
|
||||
func New() *Footer {
|
||||
footer := &Footer{
|
||||
Flex: tview.NewFlex(),
|
||||
title: Title,
|
||||
copyRight: tview.NewTextView(),
|
||||
help: tview.NewTextView(),
|
||||
}
|
||||
|
||||
footer.copyRight.
|
||||
SetDynamicColors(true).
|
||||
SetWrap(true).
|
||||
SetTextAlign(tview.AlignLeft)
|
||||
footer.copyRight.
|
||||
SetBackgroundColor(tview.Styles.PrimitiveBackgroundColor)
|
||||
footer.copyRight.SetText(fmt.Sprintf("[%s::b]%s[-:-:-]", style.GetColorHex(style.PageHeaderFgColor), fmt.Sprintf(" @ Sarv's Chatlog (%s)", version.Version)))
|
||||
|
||||
footer.help.
|
||||
SetDynamicColors(true).
|
||||
SetWrap(true).
|
||||
SetTextAlign(tview.AlignRight)
|
||||
footer.help.
|
||||
SetBackgroundColor(tview.Styles.PrimitiveBackgroundColor)
|
||||
|
||||
fmt.Fprintf(footer.help,
|
||||
"[%s::b]↑/↓[%s::b]: 导航 [%s::b]←/→[%s::b]: 切换标签 [%s::b]Enter[%s::b]: 选择 [%s::b]ESC[%s::b]: 返回 [%s::b]Ctrl+C[%s::b]: 退出",
|
||||
style.GetColorHex(style.MenuBgColor), style.GetColorHex(style.PageHeaderFgColor),
|
||||
style.GetColorHex(style.MenuBgColor), style.GetColorHex(style.PageHeaderFgColor),
|
||||
style.GetColorHex(style.MenuBgColor), style.GetColorHex(style.PageHeaderFgColor),
|
||||
style.GetColorHex(style.MenuBgColor), style.GetColorHex(style.PageHeaderFgColor),
|
||||
style.GetColorHex(style.MenuBgColor), style.GetColorHex(style.PageHeaderFgColor),
|
||||
)
|
||||
|
||||
footer.
|
||||
AddItem(footer.copyRight, 0, 1, false).
|
||||
AddItem(footer.help, 0, 1, false)
|
||||
|
||||
return footer
|
||||
}
|
||||
|
||||
func (f *Footer) SetCopyRight(text string) {
|
||||
f.copyRight.SetText(text)
|
||||
}
|
||||
|
||||
func (f *Footer) SetHelp(text string) {
|
||||
f.help.SetText(text)
|
||||
}
|
||||
87
internal/ui/help/help.go
Normal file
87
internal/ui/help/help.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package help
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sjzar/chatlog/internal/ui/style"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
const (
|
||||
Title = "help"
|
||||
ShowTitle = "帮助"
|
||||
Content = `[yellow]Chatlog 使用指南[white]
|
||||
|
||||
[green]基本操作:[white]
|
||||
• 使用 [yellow]←→[white] 键在主菜单和帮助页面之间切换
|
||||
• 使用 [yellow]↑↓[white] 键在菜单项之间移动
|
||||
• 按 [yellow]Enter[white] 选择菜单项
|
||||
• 按 [yellow]Esc[white] 返回上一级菜单
|
||||
• 按 [yellow]Ctrl+C[white] 退出程序
|
||||
|
||||
[green]使用步骤:[white]
|
||||
|
||||
[yellow]1. 获取数据密钥[white]
|
||||
选择"获取数据密钥"菜单项,程序会自动从运行中的微信进程获取密钥。
|
||||
如果有多个微信进程,会自动选择当前账号的进程。
|
||||
确保微信正在运行,否则无法获取密钥。
|
||||
|
||||
[yellow]2. 解密数据[white]
|
||||
选择"解密数据"菜单项,程序会使用获取的密钥解密微信数据库文件。
|
||||
解密后的文件会保存到工作目录中(可在设置中修改)。
|
||||
|
||||
[yellow]3. 启动 HTTP 服务[white]
|
||||
选择"启动 HTTP 服务"菜单项,启动 HTTP 和 MCP 服务。
|
||||
启动后可以通过浏览器访问 http://localhost:5030 查看聊天记录。
|
||||
|
||||
[yellow]4. 设置选项[white]
|
||||
选择"设置"菜单项,可以配置:
|
||||
• HTTP 服务端口 - 更改 HTTP 服务的监听端口
|
||||
• 工作目录 - 更改解密数据的存储位置
|
||||
|
||||
[green]HTTP API 使用:[white]
|
||||
• 聊天记录: [yellow]GET http://localhost:5030/api/v1/chatlog?time=2023-01-01&talker=wxid_xxx[white]
|
||||
• 联系人列表: [yellow]GET http://localhost:5030/api/v1/contact[white]
|
||||
• 群聊列表: [yellow]GET http://localhost:5030/api/v1/chatroom[white]
|
||||
• 会话列表: [yellow]GET http://localhost:5030/api/v1/session[white]
|
||||
|
||||
[green]MCP 集成:[white]
|
||||
Chatlog 支持 Model Context Protocol,可与支持 MCP 的 AI 助手集成。
|
||||
通过 MCP,AI 助手可以直接查询您的聊天记录、联系人和群聊信息。
|
||||
|
||||
[green]常见问题:[white]
|
||||
• 如果获取密钥失败,请确保微信程序正在运行
|
||||
• 如果解密失败,请检查密钥是否正确获取
|
||||
• 如果 HTTP 服务启动失败,请检查端口是否被占用
|
||||
• 数据目录和工作目录会自动保存,下次启动时自动加载
|
||||
|
||||
[green]数据安全:[white]
|
||||
• 所有数据处理均在本地完成,不会上传到任何外部服务器
|
||||
• 请妥善保管解密后的数据,避免隐私泄露
|
||||
`
|
||||
)
|
||||
|
||||
type Help struct {
|
||||
*tview.TextView
|
||||
title string
|
||||
}
|
||||
|
||||
func New() *Help {
|
||||
help := &Help{
|
||||
TextView: tview.NewTextView(),
|
||||
title: Title,
|
||||
}
|
||||
|
||||
help.SetDynamicColors(true)
|
||||
help.SetRegions(true)
|
||||
help.SetWrap(true)
|
||||
help.SetTextAlign(tview.AlignLeft)
|
||||
help.SetBorder(true)
|
||||
help.SetBorderColor(style.BorderColor)
|
||||
help.SetTitle(ShowTitle)
|
||||
|
||||
fmt.Fprint(help, Content)
|
||||
|
||||
return help
|
||||
}
|
||||
182
internal/ui/infobar/infobar.go
Normal file
182
internal/ui/infobar/infobar.go
Normal file
@@ -0,0 +1,182 @@
|
||||
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 = 6
|
||||
accountRow = 0
|
||||
pidRow = 1
|
||||
statusRow = 2
|
||||
dataUsageRow = 3
|
||||
workUsageRow = 4
|
||||
httpServerRow = 5
|
||||
|
||||
// 列索引
|
||||
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 和 Version 行
|
||||
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, "Version:")),
|
||||
)
|
||||
table.SetCell(accountRow, valueCol2, tview.NewTableCell(""))
|
||||
|
||||
// PID 和 ExePath 行
|
||||
table.SetCell(
|
||||
pidRow,
|
||||
labelCol1,
|
||||
tview.NewTableCell(fmt.Sprintf(" [%s::]%s", headerColor, "PID:")),
|
||||
)
|
||||
table.SetCell(pidRow, valueCol1, tview.NewTableCell(""))
|
||||
|
||||
table.SetCell(
|
||||
pidRow,
|
||||
labelCol2,
|
||||
tview.NewTableCell(fmt.Sprintf(" [%s::]%s", headerColor, "ExePath:")),
|
||||
)
|
||||
table.SetCell(pidRow, valueCol2, tview.NewTableCell(""))
|
||||
|
||||
// Status 和 Key 行
|
||||
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, "Data Key:")),
|
||||
)
|
||||
table.SetCell(statusRow, 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(""))
|
||||
|
||||
// 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(pidRow, valueCol1).SetText(fmt.Sprintf("%d", pid))
|
||||
info.table.GetCell(pidRow, valueCol2).SetText(exePath)
|
||||
info.table.GetCell(accountRow, valueCol2).SetText(version)
|
||||
}
|
||||
|
||||
func (info *InfoBar) UpdateStatus(status string) {
|
||||
info.table.GetCell(statusRow, valueCol1).SetText(status)
|
||||
}
|
||||
|
||||
func (info *InfoBar) UpdateDataKey(key string) {
|
||||
info.table.GetCell(statusRow, 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
162
internal/ui/menu/menu.go
Normal file
162
internal/ui/menu/menu.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package menu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/sjzar/chatlog/internal/ui/style"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
type Item struct {
|
||||
Index int
|
||||
Key string
|
||||
Name string
|
||||
Description string
|
||||
Hidden bool
|
||||
Selected func(i *Item)
|
||||
}
|
||||
|
||||
type Menu struct {
|
||||
*tview.Box
|
||||
title string
|
||||
table *tview.Table
|
||||
items []*Item
|
||||
}
|
||||
|
||||
func New(title string) *Menu {
|
||||
menu := &Menu{
|
||||
Box: tview.NewBox(),
|
||||
title: title,
|
||||
items: make([]*Item, 0),
|
||||
table: tview.NewTable(),
|
||||
}
|
||||
|
||||
menu.table.SetBorders(false)
|
||||
menu.table.SetSelectable(true, false)
|
||||
menu.table.SetTitle(fmt.Sprintf("[::b]%s", menu.title))
|
||||
menu.table.SetBorderColor(style.BorderColor)
|
||||
menu.table.SetBackgroundColor(style.BgColor)
|
||||
menu.table.SetTitleColor(style.FgColor)
|
||||
menu.table.SetFixed(1, 0)
|
||||
menu.table.Select(1, 0).SetSelectedFunc(func(row, column int) {
|
||||
if row == 0 {
|
||||
return // 忽略表头
|
||||
}
|
||||
|
||||
item, ok := menu.table.GetCell(row, 0).GetReference().(*Item)
|
||||
if ok {
|
||||
if item.Selected != nil {
|
||||
item.Selected(item)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
menu.setTableHeader()
|
||||
|
||||
return menu
|
||||
}
|
||||
|
||||
func (m *Menu) setTableHeader() {
|
||||
m.table.SetCell(0, 0, tview.NewTableCell(fmt.Sprintf("[black::b]%s", "命令")).
|
||||
SetExpansion(1).
|
||||
SetBackgroundColor(style.PageHeaderBgColor).
|
||||
SetTextColor(style.PageHeaderFgColor).
|
||||
SetAlign(tview.AlignLeft).
|
||||
SetSelectable(false))
|
||||
|
||||
m.table.SetCell(0, 1, tview.NewTableCell(fmt.Sprintf("[black::b]%s", "说明")).
|
||||
SetExpansion(2).
|
||||
SetBackgroundColor(style.PageHeaderBgColor).
|
||||
SetTextColor(style.PageHeaderFgColor).
|
||||
SetAlign(tview.AlignLeft).
|
||||
SetSelectable(false))
|
||||
}
|
||||
|
||||
func (m *Menu) AddItem(item *Item) {
|
||||
m.items = append(m.items, item)
|
||||
sort.Sort(SortItems(m.items))
|
||||
m.refresh()
|
||||
}
|
||||
|
||||
func (m *Menu) SetItems(items []*Item) {
|
||||
m.items = items
|
||||
m.refresh()
|
||||
}
|
||||
|
||||
func (m *Menu) GetItems() []*Item {
|
||||
return m.items
|
||||
}
|
||||
|
||||
func (m *Menu) refresh() {
|
||||
m.table.Clear()
|
||||
m.setTableHeader()
|
||||
|
||||
row := 1
|
||||
for _, item := range m.items {
|
||||
if item.Hidden {
|
||||
continue
|
||||
}
|
||||
m.table.SetCell(row, 0, tview.NewTableCell(item.Name).
|
||||
SetTextColor(style.FgColor).
|
||||
SetBackgroundColor(style.BgColor).
|
||||
SetReference(item).
|
||||
SetAlign(tview.AlignLeft))
|
||||
m.table.SetCell(row, 1, tview.NewTableCell(item.Description).
|
||||
SetTextColor(style.FgColor).
|
||||
SetBackgroundColor(style.BgColor).
|
||||
SetReference(item).
|
||||
SetAlign(tview.AlignLeft))
|
||||
row++
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *Menu) Draw(screen tcell.Screen) {
|
||||
m.refresh()
|
||||
|
||||
m.Box.DrawForSubclass(screen, m)
|
||||
m.Box.SetBorder(false)
|
||||
|
||||
menuViewX, menuViewY, menuViewW, menuViewH := m.GetInnerRect()
|
||||
|
||||
m.table.SetRect(menuViewX, menuViewY, menuViewW, menuViewH)
|
||||
m.table.SetBorder(true).SetBorderColor(style.BorderColor)
|
||||
|
||||
m.table.Draw(screen)
|
||||
}
|
||||
|
||||
func (m *Menu) Focus(delegate func(p tview.Primitive)) {
|
||||
delegate(m.table)
|
||||
}
|
||||
|
||||
// HasFocus returns whether or not this primitive has focus
|
||||
func (m *Menu) HasFocus() bool {
|
||||
// Check if the active menu has focus
|
||||
return m.table.HasFocus()
|
||||
}
|
||||
|
||||
func (m *Menu) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
|
||||
return m.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
|
||||
// 将事件传递给表格
|
||||
if handler := m.table.InputHandler(); handler != nil {
|
||||
handler(event, setFocus)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type SortItems []*Item
|
||||
|
||||
func (l SortItems) Len() int {
|
||||
return len(l)
|
||||
}
|
||||
|
||||
func (l SortItems) Less(i, j int) bool {
|
||||
return l[i].Index < l[j].Index
|
||||
}
|
||||
|
||||
func (l SortItems) Swap(i, j int) {
|
||||
l[i], l[j] = l[j], l[i]
|
||||
}
|
||||
232
internal/ui/menu/submenu.go
Normal file
232
internal/ui/menu/submenu.go
Normal file
@@ -0,0 +1,232 @@
|
||||
package menu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/sjzar/chatlog/internal/ui/style"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
const (
|
||||
// DialogPadding dialog inner paddign.
|
||||
DialogPadding = 3
|
||||
|
||||
// DialogFormHeight dialog "Enter"/"Cancel" form height.
|
||||
DialogHelpHeight = 1
|
||||
|
||||
// DialogMinWidth dialog min width.
|
||||
DialogMinWidth = 40
|
||||
|
||||
// TableHeightOffset table height offset for border.
|
||||
TableHeightOffset = 3
|
||||
|
||||
cmdWidthOffset = 6
|
||||
)
|
||||
|
||||
type SubMenu struct {
|
||||
*tview.Box
|
||||
title string
|
||||
layout *tview.Flex
|
||||
table *tview.Table
|
||||
width int
|
||||
height int
|
||||
items []*Item
|
||||
cancelHandler func()
|
||||
}
|
||||
|
||||
func NewSubMenu(title string) *SubMenu {
|
||||
subMenu := &SubMenu{
|
||||
Box: tview.NewBox(),
|
||||
title: title,
|
||||
items: make([]*Item, 0),
|
||||
layout: tview.NewFlex(),
|
||||
table: tview.NewTable(),
|
||||
}
|
||||
|
||||
subMenu.table.SetBorders(false)
|
||||
subMenu.table.SetSelectable(true, false)
|
||||
subMenu.table.SetBorderColor(style.DialogBorderColor)
|
||||
subMenu.table.SetBackgroundColor(style.DialogBgColor)
|
||||
subMenu.table.SetTitleColor(style.DialogFgColor)
|
||||
subMenu.table.SetFixed(1, 1)
|
||||
|
||||
subMenu.table.Select(1, 0).SetSelectedFunc(func(row, column int) {
|
||||
if row == 0 {
|
||||
return // 忽略表头
|
||||
}
|
||||
|
||||
item := subMenu.items[row-1]
|
||||
if item.Selected != nil {
|
||||
item.Selected(item)
|
||||
}
|
||||
})
|
||||
|
||||
subMenu.setTableHeader()
|
||||
|
||||
// 帮助信息
|
||||
helpText := tview.NewTextView()
|
||||
helpText.SetDynamicColors(true)
|
||||
helpText.SetTextAlign(tview.AlignCenter)
|
||||
helpText.SetTextColor(style.DialogFgColor)
|
||||
helpText.SetBackgroundColor(style.DialogBgColor)
|
||||
fmt.Fprintf(helpText,
|
||||
"[%s::b]↑/↓[%s::b]: 导航 [%s::b]Enter[%s::b]: 选择 [%s::b]ESC[%s::b]: 返回",
|
||||
style.GetColorHex(style.MenuBgColor), style.GetColorHex(style.PageHeaderFgColor),
|
||||
style.GetColorHex(style.MenuBgColor), style.GetColorHex(style.PageHeaderFgColor),
|
||||
style.GetColorHex(style.MenuBgColor), style.GetColorHex(style.PageHeaderFgColor),
|
||||
)
|
||||
|
||||
// 布局
|
||||
tableLayout := tview.NewFlex().SetDirection(tview.FlexColumn)
|
||||
tableLayout.AddItem(EmptyBoxSpace(style.DialogBgColor), 1, 0, true)
|
||||
tableLayout.AddItem(subMenu.table, 0, 1, true)
|
||||
tableLayout.AddItem(EmptyBoxSpace(style.DialogBgColor), 1, 0, true)
|
||||
|
||||
subMenu.layout.SetDirection(tview.FlexRow)
|
||||
subMenu.layout.SetTitle(fmt.Sprintf("[::b]%s", subMenu.title))
|
||||
subMenu.layout.SetTitleColor(style.DialogFgColor)
|
||||
subMenu.layout.SetTitleAlign(tview.AlignCenter)
|
||||
subMenu.layout.AddItem(tableLayout, 0, 1, true)
|
||||
subMenu.layout.AddItem(helpText, DialogHelpHeight, 0, true)
|
||||
subMenu.layout.SetBorder(true)
|
||||
subMenu.layout.SetBorderColor(style.DialogBorderColor)
|
||||
subMenu.layout.SetBackgroundColor(style.DialogBgColor)
|
||||
|
||||
return subMenu
|
||||
}
|
||||
|
||||
func (m *SubMenu) setTableHeader() {
|
||||
m.table.SetCell(0, 0, tview.NewTableCell(fmt.Sprintf("[%s::b]%s", style.GetColorHex(style.TableHeaderFgColor), "命令")).
|
||||
SetExpansion(1).
|
||||
SetBackgroundColor(style.TableHeaderBgColor).
|
||||
SetTextColor(style.TableHeaderFgColor).
|
||||
SetAlign(tview.AlignLeft).
|
||||
SetSelectable(false))
|
||||
|
||||
m.table.SetCell(0, 1, tview.NewTableCell(fmt.Sprintf("[%s::b]%s", style.GetColorHex(style.TableHeaderFgColor), "说明")).
|
||||
SetExpansion(1).
|
||||
SetBackgroundColor(style.TableHeaderBgColor).
|
||||
SetTextColor(style.TableHeaderFgColor).
|
||||
SetAlign(tview.AlignLeft).
|
||||
SetSelectable(false))
|
||||
}
|
||||
|
||||
func (m *SubMenu) AddItem(item *Item) {
|
||||
m.items = append(m.items, item)
|
||||
sort.Sort(SortItems(m.items))
|
||||
m.refresh()
|
||||
}
|
||||
|
||||
func (m *SubMenu) SetItems(items []*Item) {
|
||||
m.items = items
|
||||
m.refresh()
|
||||
}
|
||||
|
||||
func (m *SubMenu) SetCancelFunc(handler func()) *SubMenu {
|
||||
m.cancelHandler = handler
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *SubMenu) refresh() {
|
||||
m.table.Clear()
|
||||
m.setTableHeader()
|
||||
|
||||
col1Width := 0
|
||||
col2Width := 0
|
||||
|
||||
row := 1
|
||||
for _, item := range m.items {
|
||||
if item.Hidden {
|
||||
continue
|
||||
}
|
||||
m.table.SetCell(row, 0, tview.NewTableCell(item.Name).
|
||||
SetTextColor(style.DialogFgColor).
|
||||
SetBackgroundColor(style.DialogBgColor).
|
||||
SetReference(item).
|
||||
SetAlign(tview.AlignLeft))
|
||||
m.table.SetCell(row, 1, tview.NewTableCell(item.Description).
|
||||
SetTextColor(style.DialogFgColor).
|
||||
SetBackgroundColor(style.DialogBgColor).
|
||||
SetReference(item).
|
||||
SetAlign(tview.AlignLeft))
|
||||
if len(item.Name) > col1Width {
|
||||
col1Width = len(item.Name)
|
||||
}
|
||||
if len(item.Description) > col2Width {
|
||||
col2Width = len(item.Description)
|
||||
}
|
||||
row++
|
||||
}
|
||||
|
||||
m.width = col1Width + col2Width + 2 + cmdWidthOffset
|
||||
m.height = len(m.items) + TableHeightOffset + DialogHelpHeight + 1
|
||||
|
||||
}
|
||||
|
||||
func (m *SubMenu) Draw(screen tcell.Screen) {
|
||||
m.refresh()
|
||||
|
||||
m.Box.DrawForSubclass(screen, m)
|
||||
m.layout.Draw(screen)
|
||||
}
|
||||
|
||||
func (m *SubMenu) SetRect(x, y, width, height int) {
|
||||
ws := (width - m.width) / 2
|
||||
hs := ((height - m.height) / 2)
|
||||
dy := y + hs
|
||||
bWidth := m.width
|
||||
|
||||
if m.width > width {
|
||||
ws = 0
|
||||
bWidth = width - 1
|
||||
}
|
||||
|
||||
bHeight := m.height
|
||||
|
||||
if m.height >= height {
|
||||
dy = y + 1
|
||||
bHeight = height - 1
|
||||
}
|
||||
|
||||
m.Box.SetRect(x+ws, dy, bWidth, bHeight)
|
||||
|
||||
x, y, width, height = m.Box.GetInnerRect()
|
||||
|
||||
m.layout.SetRect(x, y, width, height)
|
||||
}
|
||||
|
||||
func (m *SubMenu) Focus(delegate func(p tview.Primitive)) {
|
||||
delegate(m.table)
|
||||
}
|
||||
|
||||
// HasFocus returns whether or not this primitive has focus
|
||||
func (m *SubMenu) HasFocus() bool {
|
||||
// Check if the active menu has focus
|
||||
return m.table.HasFocus()
|
||||
}
|
||||
|
||||
func (m *SubMenu) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
|
||||
return m.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
|
||||
|
||||
if event.Key() == tcell.KeyEscape && m.cancelHandler != nil {
|
||||
m.cancelHandler()
|
||||
return
|
||||
}
|
||||
|
||||
// 将事件传递给表格
|
||||
if handler := m.table.InputHandler(); handler != nil {
|
||||
handler(event, setFocus)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func EmptyBoxSpace(bgColor tcell.Color) *tview.Box {
|
||||
box := tview.NewBox()
|
||||
box.SetBackgroundColor(bgColor)
|
||||
box.SetBorder(false)
|
||||
|
||||
return box
|
||||
}
|
||||
78
internal/ui/style/style.go
Normal file
78
internal/ui/style/style.go
Normal file
@@ -0,0 +1,78 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package style
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
const (
|
||||
// HeavyGreenCheckMark unicode.
|
||||
HeavyGreenCheckMark = "\u2705"
|
||||
// HeavyRedCrossMark unicode.
|
||||
HeavyRedCrossMark = "\u274C"
|
||||
// ProgressBar cell.
|
||||
ProgressBarCell = "▉"
|
||||
)
|
||||
|
||||
var (
|
||||
// infobar.
|
||||
InfoBarItemFgColor = tcell.ColorSilver
|
||||
// main views.
|
||||
FgColor = tcell.ColorFloralWhite
|
||||
BgColor = tview.Styles.PrimitiveBackgroundColor
|
||||
BorderColor = tcell.NewRGBColor(135, 175, 146) //nolint:mnd
|
||||
HelpHeaderFgColor = tcell.NewRGBColor(135, 175, 146) //nolint:mnd
|
||||
MenuBgColor = tcell.ColorMediumSeaGreen
|
||||
PageHeaderBgColor = tcell.ColorMediumSeaGreen
|
||||
PageHeaderFgColor = tcell.ColorFloralWhite
|
||||
RunningStatusFgColor = tcell.NewRGBColor(95, 215, 0) //nolint:mnd
|
||||
PausedStatusFgColor = tcell.NewRGBColor(255, 175, 0) //nolint:mnd
|
||||
// dialogs.
|
||||
DialogBgColor = tcell.NewRGBColor(38, 38, 38) //nolint:mnd
|
||||
DialogBorderColor = tcell.ColorMediumSeaGreen
|
||||
DialogFgColor = tcell.ColorFloralWhite
|
||||
DialogSubBoxBorderColor = tcell.ColorDimGray
|
||||
ErrorDialogBgColor = tcell.NewRGBColor(215, 0, 0) //nolint:mnd
|
||||
ErrorDialogButtonBgColor = tcell.ColorDarkRed
|
||||
// terminal.
|
||||
TerminalFgColor = tcell.ColorFloralWhite
|
||||
TerminalBgColor = tcell.NewRGBColor(5, 5, 5) //nolint:mnd
|
||||
TerminalBorderColor = tcell.ColorDimGray
|
||||
// table header.
|
||||
TableHeaderBgColor = tcell.ColorMediumSeaGreen
|
||||
TableHeaderFgColor = tcell.ColorFloralWhite
|
||||
// progress bar.
|
||||
PrgBgColor = tcell.ColorDimGray
|
||||
PrgBarColor = tcell.ColorDarkOrange
|
||||
PrgBarEmptyColor = tcell.ColorWhite
|
||||
PrgBarOKColor = tcell.ColorGreen
|
||||
PrgBarWarnColor = tcell.ColorOrange
|
||||
PrgBarCritColor = tcell.ColorRed
|
||||
// dropdown.
|
||||
DropDownUnselected = tcell.StyleDefault.Background(tcell.ColorWhiteSmoke).Foreground(tcell.ColorBlack)
|
||||
DropDownSelected = tcell.StyleDefault.Background(tcell.ColorLightSlateGray).Foreground(tcell.ColorWhite)
|
||||
// other primitives.
|
||||
InputFieldBgColor = tcell.ColorGray
|
||||
ButtonBgColor = tcell.ColorMediumSeaGreen
|
||||
)
|
||||
|
||||
// GetColorName returns convert tcell color to its name.
|
||||
func GetColorName(color tcell.Color) string {
|
||||
for name, c := range tcell.ColorNames {
|
||||
if c == color {
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetColorHex returns convert tcell color to its hex useful for textview primitives.
|
||||
func GetColorHex(color tcell.Color) string {
|
||||
return fmt.Sprintf("#%x", color.Hex())
|
||||
}
|
||||
81
internal/ui/style/style_windows.go
Normal file
81
internal/ui/style/style_windows.go
Normal file
@@ -0,0 +1,81 @@
|
||||
//go:build windows
|
||||
|
||||
package style
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
const (
|
||||
// HeavyGreenCheckMark unicode.
|
||||
HeavyGreenCheckMark = "[green::]\u25CF[-::]"
|
||||
// HeavyRedCrossMark unicode.
|
||||
HeavyRedCrossMark = "[red::]\u25CF[-::]"
|
||||
// ProgressBar cell.
|
||||
ProgressBarCell = "\u2593"
|
||||
)
|
||||
|
||||
var (
|
||||
// infobar.
|
||||
InfoBarItemFgColor = tcell.ColorGray
|
||||
// main views.
|
||||
FgColor = tview.Styles.PrimaryTextColor
|
||||
BgColor = tview.Styles.PrimitiveBackgroundColor
|
||||
BorderColor = tcell.ColorSpringGreen
|
||||
MenuBgColor = tcell.ColorSpringGreen
|
||||
HelpHeaderFgColor = tcell.ColorSpringGreen
|
||||
PageHeaderBgColor = tcell.ColorSpringGreen
|
||||
PageHeaderFgColor = tview.Styles.PrimaryTextColor
|
||||
RunningStatusFgColor = tcell.ColorLime
|
||||
PausedStatusFgColor = tcell.ColorYellow
|
||||
|
||||
// dialogs.
|
||||
DialogBgColor = tview.Styles.PrimitiveBackgroundColor
|
||||
DialogFgColor = tview.Styles.PrimaryTextColor
|
||||
DialogBorderColor = tcell.ColorSpringGreen
|
||||
DialogSubBoxBorderColor = tcell.ColorGray
|
||||
ErrorDialogBgColor = tcell.ColorRed
|
||||
ErrorDialogButtonBgColor = tcell.ColorSpringGreen
|
||||
// terminal.
|
||||
TerminalBgColor = tview.Styles.PrimitiveBackgroundColor
|
||||
TerminalFgColor = tview.Styles.PrimaryTextColor
|
||||
TerminalBorderColor = tview.Styles.PrimitiveBackgroundColor
|
||||
// table header.
|
||||
TableHeaderBgColor = tcell.ColorSpringGreen
|
||||
TableHeaderFgColor = tview.Styles.PrimaryTextColor
|
||||
// progress bar.
|
||||
PrgBgColor = tview.Styles.PrimaryTextColor
|
||||
PrgBarColor = tcell.ColorFuchsia
|
||||
PrgBarEmptyColor = tcell.ColorWhite
|
||||
PrgBarOKColor = tcell.ColorLime
|
||||
PrgBarWarnColor = tcell.ColorYellow
|
||||
PrgBarCritColor = tcell.ColorRed
|
||||
// dropdown.
|
||||
DropDownUnselected = tcell.StyleDefault.Background(tcell.ColorGray).Foreground(tcell.ColorWhite)
|
||||
DropDownSelected = tcell.StyleDefault.Background(tcell.ColorPurple).Foreground(tview.Styles.PrimaryTextColor)
|
||||
// other primitives.
|
||||
InputFieldBgColor = tcell.ColorGray
|
||||
ButtonBgColor = tcell.ColorSpringGreen
|
||||
)
|
||||
|
||||
// GetColorName returns convert tcell color to its name.
|
||||
func GetColorName(color tcell.Color) string {
|
||||
for name, c := range tcell.ColorNames {
|
||||
if c == color {
|
||||
return name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetColorHex shall returns convert tcell color to its hex useful for textview primitives,
|
||||
// however, for windows nodes it will return color name.
|
||||
func GetColorHex(color tcell.Color) string {
|
||||
for name, c := range tcell.ColorNames {
|
||||
if c == color {
|
||||
return name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user