init
This commit is contained in:
230
tui/tui.go
Normal file
230
tui/tui.go
Normal file
@ -0,0 +1,230 @@
|
||||
// go get github.com/rivo/tview@master
|
||||
package tui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
node "git.zzyxyz.com/zzy/goncdu/node"
|
||||
"git.zzyxyz.com/zzy/goncdu/utils"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
type UI struct {
|
||||
app *tview.Application
|
||||
main *tview.Frame
|
||||
msg string
|
||||
err string
|
||||
list *tview.List
|
||||
node *node.NodeManager
|
||||
nodes []*node.PathNode
|
||||
inputPath *tview.InputField
|
||||
}
|
||||
|
||||
func NewUI(n *node.NodeManager) *UI {
|
||||
list := tview.NewList()
|
||||
list.ShowSecondaryText(false).
|
||||
SetTitleAlign(tview.AlignLeft).
|
||||
SetTitleColor(tcell.ColorWhite)
|
||||
frame := tview.NewFrame(list).
|
||||
SetBorders(0, 0, 0, 0, 1, 1)
|
||||
|
||||
ui := &UI{
|
||||
app: tview.NewApplication().SetRoot(frame, true),
|
||||
main: frame,
|
||||
msg: "",
|
||||
err: "",
|
||||
list: list,
|
||||
node: n,
|
||||
}
|
||||
|
||||
ui.list.SetInputCapture(
|
||||
func(event *tcell.EventKey) *tcell.EventKey {
|
||||
switch event.Key() {
|
||||
case tcell.KeyRight:
|
||||
idx := ui.list.GetCurrentItem()
|
||||
if len(ui.nodes) == 0 {
|
||||
ui.err = errors.New("no children nodes").Error()
|
||||
break
|
||||
}
|
||||
name := ui.nodes[idx].GetPath()
|
||||
if err := ui.node.GoIn(name); err != nil {
|
||||
ui.err = err.Error()
|
||||
break
|
||||
}
|
||||
case tcell.KeyLeft:
|
||||
if err := ui.node.GoOut(); err != nil {
|
||||
ui.err = err.Error()
|
||||
}
|
||||
case tcell.KeyRune:
|
||||
switch event.Rune() {
|
||||
case 'q':
|
||||
ui.app.Stop()
|
||||
case '?':
|
||||
// show help
|
||||
ui.showHelp()
|
||||
case 'R':
|
||||
ui.app.SetRoot(ui.inputPath, true)
|
||||
case 'S':
|
||||
if err := ui.node.SaveToFile(); err != nil {
|
||||
ui.err = err.Error()
|
||||
}
|
||||
case 'L':
|
||||
if err := ui.node.LoadFromFile(); err != nil {
|
||||
ui.err = err.Error()
|
||||
}
|
||||
default:
|
||||
return event
|
||||
}
|
||||
default:
|
||||
return event
|
||||
}
|
||||
|
||||
// END:
|
||||
ui.renderList()
|
||||
return nil
|
||||
})
|
||||
return ui
|
||||
}
|
||||
|
||||
func (ui *UI) showHelp() {
|
||||
helpText := []string{
|
||||
"goncdu Help",
|
||||
"-------------------------",
|
||||
"Use the arrow keys to navigate:",
|
||||
" ↑ (up) : Move up",
|
||||
" ↓ (down) : Move down",
|
||||
" → (right) : Enter directory",
|
||||
" ← (left) : Go up one directory",
|
||||
"-------------------------",
|
||||
"Other commands:",
|
||||
" R : ReInput scan path",
|
||||
" f : Refresh cache",
|
||||
" q : Quit",
|
||||
" ? : Show this help message",
|
||||
"-------------------------",
|
||||
"Press q, Enter, Esc key to return",
|
||||
}
|
||||
|
||||
// 调整字符串长度使其一致
|
||||
maxLength := 0
|
||||
for _, line := range helpText {
|
||||
if len(line) > maxLength {
|
||||
maxLength = len(line)
|
||||
}
|
||||
}
|
||||
|
||||
var helpContent strings.Builder
|
||||
for _, line := range helpText {
|
||||
paddedLine := fmt.Sprintf("%-*s", maxLength, line)
|
||||
helpContent.WriteString(paddedLine)
|
||||
helpContent.WriteString("\n")
|
||||
}
|
||||
|
||||
// 创建一个模态窗口并添加 TextView
|
||||
modal := tview.NewModal().
|
||||
SetText(helpContent.String())
|
||||
|
||||
modal.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyEscape || event.Key() == tcell.KeyEnter ||
|
||||
(event.Key() == tcell.KeyRune && event.Rune() == 'q') {
|
||||
ui.app.SetRoot(ui.main, true)
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
// 设置模态窗口为应用的根节点
|
||||
ui.app.SetRoot(modal, false)
|
||||
}
|
||||
func (ui *UI) drawHeaderFooter() {
|
||||
curNode := ui.node.GetCurrentNode()
|
||||
ui.main.Clear().
|
||||
AddText("goncdu test ~ Use the arrow keys to navigate, press ? for help",
|
||||
true, tview.AlignLeft, tcell.ColorWhite).
|
||||
AddText(fmt.Sprintf("--- %s", curNode.GetPath()),
|
||||
true, tview.AlignLeft, tcell.ColorWhite).
|
||||
AddText(fmt.Sprintf("Total Size = %s Items = %d --- Scaning time = %s",
|
||||
node.FormatSize(curNode.GetSize()), curNode.GetCount()-1, ui.node.GetScanTime()),
|
||||
false, tview.AlignLeft, tcell.ColorWhite).
|
||||
AddText(ui.err, false, tview.AlignLeft, tcell.ColorGrey)
|
||||
}
|
||||
|
||||
func (ui *UI) drawList(items []string) {
|
||||
ui.list.Clear()
|
||||
for _, item := range items {
|
||||
ui.list.AddItem(item, "", 0, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (ui *UI) run() {
|
||||
if err := ui.app.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (ui *UI) renderList() {
|
||||
icon_type := map[node.PathNodeType]string{
|
||||
node.DIR: "📁",
|
||||
node.FILE: "📄",
|
||||
node.UNKNOWN: "❓",
|
||||
}
|
||||
|
||||
nodes := ui.node.GetChildrenWithSorted()
|
||||
ui.nodes = nodes
|
||||
|
||||
renderLine := func(entry *node.PathNode) string {
|
||||
return fmt.Sprintf("%s %10s %10s %s\n", icon_type[entry.GetType()],
|
||||
node.FormatSize(entry.GetSize()),
|
||||
utils.FormatProgressBar(entry.GetSize(), nodes[0].GetSize(), 10,
|
||||
[]string{" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"}),
|
||||
entry.GetPath())
|
||||
}
|
||||
|
||||
str_list := make([]string, len(nodes))
|
||||
for idx, child := range nodes {
|
||||
ret := renderLine(child)
|
||||
str_list[idx] = ret
|
||||
}
|
||||
ui.drawList(str_list)
|
||||
ui.drawHeaderFooter()
|
||||
}
|
||||
|
||||
func (ui *UI) scanPath() {
|
||||
err := ui.node.Scan()
|
||||
if err != nil {
|
||||
ui.err = err.Error()
|
||||
}
|
||||
ui.renderList()
|
||||
}
|
||||
|
||||
func ShowMain(entry *node.NodeManager) {
|
||||
ui := NewUI(entry)
|
||||
|
||||
// need input path
|
||||
inputPath := tview.NewInputField()
|
||||
ui.inputPath = inputPath
|
||||
inputPath.SetLabel("Input Path: ").
|
||||
SetFieldWidth(4096).
|
||||
SetDoneFunc(func(key tcell.Key) {
|
||||
if key == tcell.KeyEnter {
|
||||
path := inputPath.GetText()
|
||||
valid := ui.node.SetRootPath(path)
|
||||
if valid {
|
||||
ui.app.SetRoot(ui.main, true)
|
||||
ui.scanPath()
|
||||
} else {
|
||||
inputPath.SetLabel("Invalid Path")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if entry.VaildRoot() {
|
||||
ui.app.SetRoot(ui.main, true)
|
||||
ui.scanPath()
|
||||
} else {
|
||||
ui.app.SetRoot(inputPath, true)
|
||||
}
|
||||
ui.run()
|
||||
}
|
56
tui/utils.go
Normal file
56
tui/utils.go
Normal file
@ -0,0 +1,56 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// TruncateStr 截断字符串以适应给定宽度
|
||||
func TruncateStr(inStr string, maxWidth int) string {
|
||||
if len(inStr) <= maxWidth {
|
||||
return inStr
|
||||
}
|
||||
headLen := (maxWidth - 3) / 2
|
||||
tailLen := maxWidth - headLen - 3
|
||||
return fmt.Sprintf("%s...%s", inStr[:headLen], inStr[len(inStr)-tailLen:])
|
||||
}
|
||||
|
||||
// FormatProgressBar generates a progress bar string.
|
||||
func FormatProgressBar(size, maxSize int64, width int, blockChars []string) string {
|
||||
if blockChars == nil || len(blockChars) < 2 {
|
||||
blockChars = []string{" ", "#"}
|
||||
}
|
||||
|
||||
if maxSize == 0 {
|
||||
return "[" + repeatString(blockChars[0], width) + "]"
|
||||
}
|
||||
|
||||
ratio := float64(size) / float64(maxSize)
|
||||
filledLength := int(ratio * float64(width))
|
||||
remainingLength := width - filledLength
|
||||
|
||||
// Calculate the fractional part
|
||||
fractionalPart := math.Mod(ratio*float64(width), 1.0)
|
||||
|
||||
// Create the progress bar string
|
||||
progressBar := repeatString(blockChars[len(blockChars)-1], filledLength)
|
||||
|
||||
if fractionalPart > 0 && filledLength < width {
|
||||
// Calculate the index for the fractional part
|
||||
fractionalIndex := int(fractionalPart*float64(len(blockChars)-2)) + 1
|
||||
progressBar += blockChars[fractionalIndex]
|
||||
remainingLength--
|
||||
}
|
||||
|
||||
progressBar += repeatString(blockChars[0], remainingLength)
|
||||
return "[" + progressBar + "]"
|
||||
}
|
||||
|
||||
// repeatString repeats a string n times.
|
||||
func repeatString(s string, n int) string {
|
||||
result := ""
|
||||
for i := 0; i < n; i++ {
|
||||
result += s
|
||||
}
|
||||
return result
|
||||
}
|
Reference in New Issue
Block a user