|
1 | 1 | package src |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "fmt" |
4 | 5 | "strings" |
5 | 6 |
|
6 | 7 | "github.com/charmbracelet/lipgloss" |
7 | 8 | ) |
8 | 9 |
|
9 | 10 | func (m Model) View() string { |
10 | 11 | if m.quitting { |
11 | | - return "Goodbye!\n" |
| 12 | + return lipgloss.NewStyle().Foreground(lipgloss.Color(colorMuted)).Render("Goodbye from ngt.\n") |
12 | 13 | } |
13 | 14 | if m.subShell { |
14 | 15 | return "" |
15 | 16 | } |
16 | | - title := titleStyle.Render(appName + " " + version) |
17 | | - help := subtitleStyle.Render(strings.Join([]string{ |
18 | | - helpString(m.keys.quit), |
19 | | - helpString(m.keys.execute), |
20 | | - helpString(m.keys.refresh), |
21 | | - helpString(m.keys.back), |
22 | | - helpString(m.keys.selectIt), |
23 | | - helpString(m.keys.filter), |
24 | | - helpString(m.keys.tab), |
25 | | - helpString(m.keys.copy), |
26 | | - helpString(m.keys.move), |
27 | | - helpString(m.keys.delete), |
28 | | - helpString(m.keys.fuzzy), |
29 | | - helpString(m.keys.bulkRename), |
30 | | - helpString(m.keys.suspend), |
31 | | - helpString(m.keys.subshell), |
32 | | - helpString(m.keys.help), |
33 | | - }, " • ")) |
34 | | - fBar := fBarStyle.Render(fBarContent) |
| 17 | + |
| 18 | + w := m.termW |
| 19 | + if w < 40 { |
| 20 | + w = 80 |
| 21 | + } |
| 22 | + |
| 23 | + // ── Title bar ────────────────────────────────────────────────────────────── |
| 24 | + titleBar := titleBarStyle.Width(w).Render( |
| 25 | + appNameStyle.Render(" ngt ") + |
| 26 | + versionStyle.Render(version) + |
| 27 | + lipgloss.NewStyle().Foreground(lipgloss.Color(colorDim)).Render(" │ HackerOS file manager"), |
| 28 | + ) |
| 29 | + |
| 30 | + // ── Function bar ────────────────────────────────────────────────────────── |
| 31 | + fBar := renderFBar(w) |
| 32 | + |
| 33 | + // ── Progress mode ───────────────────────────────────────────────────────── |
35 | 34 | if m.mode == progressMode { |
36 | | - return lipgloss.JoinVertical(lipgloss.Left, title, m.progress.View(), m.statusMsg, fBar, help) |
| 35 | + prog := lipgloss.NewStyle().Padding(1, 2).Width(w).Render(m.progress.View()) |
| 36 | + status := statusBarStyle.Width(w).Render(m.statusMsg) |
| 37 | + return lipgloss.JoinVertical(lipgloss.Left, titleBar, prog, status, fBar) |
37 | 38 | } |
| 39 | + |
| 40 | + // ── Editor mode ─────────────────────────────────────────────────────────── |
38 | 41 | if m.mode == editorMode { |
39 | | - help = subtitleStyle.Render(helpString(m.keys.save) + " • " + helpString(m.keys.cancel)) |
40 | | - editorView := editorStyle.Render(m.editor.View()) |
41 | | - status := m.statusMsg |
42 | | - return lipgloss.JoinVertical(lipgloss.Left, title, subtitleStyle.Render("Editing: "+m.editorFile), editorView, status, fBar, help) |
| 42 | + editorBar := titleBarStyle.Width(w).Render( |
| 43 | + " ✎ Editing: " + lipgloss.NewStyle().Foreground(lipgloss.Color(colorAccent)).Render(m.editorFile) + |
| 44 | + lipgloss.NewStyle().Foreground(lipgloss.Color(colorMuted)).Render(" Ctrl+S save • Esc cancel"), |
| 45 | + ) |
| 46 | + editorView := editorStyle.Width(w - 2).Render(m.editor.View()) |
| 47 | + status := statusBarStyle.Width(w).Render(m.statusMsg) |
| 48 | + return lipgloss.JoinVertical(lipgloss.Left, titleBar, editorBar, editorView, status, fBar) |
43 | 49 | } |
| 50 | + |
| 51 | + // ── Fuzzy mode ──────────────────────────────────────────────────────────── |
44 | 52 | if m.mode == fuzzyMode { |
45 | | - fuzzyView := inputStyle.Render(m.fuzzyInput.View()) |
46 | | - listView := listStyle.Render(m.panels[m.activePanel].fileList.View()) |
47 | | - return lipgloss.JoinVertical(lipgloss.Left, title, fuzzyView, listView, m.statusMsg, fBar, help) |
| 53 | + searchBar := inputStyle.Width(w - 2).Render(" 🔍 " + m.fuzzyInput.View()) |
| 54 | + listView := inactivePanelBorder.Width(w - 2).Render(m.panels[m.activePanel].fileList.View()) |
| 55 | + status := statusBarStyle.Width(w).Render( |
| 56 | + m.statusMsg + lipgloss.NewStyle().Foreground(lipgloss.Color(colorMuted)).Render( |
| 57 | + fmt.Sprintf(" %d results • Enter: navigate • Esc: cancel", len(m.fuzzyResults)), |
| 58 | + ), |
| 59 | + ) |
| 60 | + return lipgloss.JoinVertical(lipgloss.Left, titleBar, searchBar, listView, status, fBar) |
48 | 61 | } |
| 62 | + |
| 63 | + // ── Bulk rename mode ────────────────────────────────────────────────────── |
49 | 64 | if m.mode == bulkRenameMode { |
50 | | - inputView := inputStyle.Render(m.commandInput.View()) |
51 | | - return lipgloss.JoinVertical(lipgloss.Left, title, "Bulk Rename", inputView, m.statusMsg, fBar, help) |
| 65 | + step := "Step 1/2: regex pattern" |
| 66 | + if m.bulkRenameFrom != "" { |
| 67 | + step = "Step 2/2: replacement string" |
| 68 | + } |
| 69 | + header := titleBarStyle.Width(w).Render(" ✎ Bulk Rename " + |
| 70 | + lipgloss.NewStyle().Foreground(lipgloss.Color(colorMuted)).Render(step)) |
| 71 | + inputView := inputStyle.Width(w - 2).Render(m.commandInput.View()) |
| 72 | + status := statusBarStyle.Width(w).Render(m.statusMsg) |
| 73 | + return lipgloss.JoinVertical(lipgloss.Left, titleBar, header, inputView, status, fBar) |
| 74 | + } |
| 75 | + |
| 76 | + // ── Confirmation mode ───────────────────────────────────────────────────── |
| 77 | + if m.mode == confirmMode { |
| 78 | + dialog := dialogStyle.Width(50).Render( |
| 79 | + errorStyle.Render("⚠ Confirm Action") + "\n\n" + |
| 80 | + m.confirmMsg + "\n\n" + |
| 81 | + lipgloss.NewStyle().Foreground(lipgloss.Color(colorMuted)).Render("Press y to confirm, n to cancel"), |
| 82 | + ) |
| 83 | + centered := lipgloss.Place(w, 10, lipgloss.Center, lipgloss.Center, dialog) |
| 84 | + return lipgloss.JoinVertical(lipgloss.Left, titleBar, centered, fBar) |
52 | 85 | } |
53 | | - leftList := listStyle.Width(m.panels[0].fileList.Width()).Render(m.panels[0].fileList.View()) |
54 | | - rightList := listStyle.Width(m.panels[1].fileList.Width()).Render(m.panels[1].fileList.View()) |
55 | | - content := lipgloss.JoinHorizontal(lipgloss.Top, leftList, rightList) |
56 | | - leftPwd := subtitleStyle.Render("Left: " + m.panels[0].currentDir) |
57 | | - if m.panels[0].gitBranch != "" { |
58 | | - leftPwd += " [Branch: " + m.panels[0].gitBranch + "]" |
| 86 | + |
| 87 | + // ── Podman browser mode ─────────────────────────────────────────────────── |
| 88 | + if m.mode == podmanMode { |
| 89 | + header := podmanStyle.Render(" 🐳 Podman Containers ") + |
| 90 | + lipgloss.NewStyle().Foreground(lipgloss.Color(colorMuted)).Render(" Enter: connect • Esc: cancel") |
| 91 | + var rows []string |
| 92 | + for i, c := range m.podmanContainers { |
| 93 | + prefix := " " |
| 94 | + if i == 0 { |
| 95 | + prefix = lipgloss.NewStyle().Foreground(lipgloss.Color(colorAccent)).Render("▶ ") |
| 96 | + } |
| 97 | + rows = append(rows, prefix+c) |
| 98 | + } |
| 99 | + if len(rows) == 0 { |
| 100 | + rows = []string{lipgloss.NewStyle().Foreground(lipgloss.Color(colorMuted)).Render(" No running containers found")} |
| 101 | + } |
| 102 | + box := inactivePanelBorder.Width(w - 2).Render(strings.Join(rows, "\n")) |
| 103 | + return lipgloss.JoinVertical(lipgloss.Left, titleBar, header, box, fBar) |
59 | 104 | } |
60 | | - rightPwd := subtitleStyle.Render("Right: " + m.panels[1].currentDir) |
61 | | - if m.panels[1].gitBranch != "" { |
62 | | - rightPwd += " [Branch: " + m.panels[1].gitBranch + "]" |
| 105 | + |
| 106 | + // ── Explorer mode (main) ────────────────────────────────────────────────── |
| 107 | + halfW := (w / 2) - 3 |
| 108 | + |
| 109 | + // Path bars |
| 110 | + leftPath := m.renderPathBar(0, halfW) |
| 111 | + rightPath := m.renderPathBar(1, halfW) |
| 112 | + pathRow := lipgloss.JoinHorizontal(lipgloss.Top, leftPath, rightPath) |
| 113 | + |
| 114 | + // File panels |
| 115 | + leftPanel := m.renderPanel(0, halfW) |
| 116 | + rightPanel := m.renderPanel(1, halfW) |
| 117 | + panels := lipgloss.JoinHorizontal(lipgloss.Top, leftPanel, rightPanel) |
| 118 | + |
| 119 | + // Command input |
| 120 | + cmdLabel := inputLabelStyle.Render("›") |
| 121 | + inputView := inputStyle.Width(w - 6).Render(m.commandInput.View()) |
| 122 | + cmdRow := lipgloss.JoinHorizontal(lipgloss.Left, cmdLabel, inputView) |
| 123 | + |
| 124 | + // Status |
| 125 | + status := statusBarStyle.Width(w).Render(m.statusMsg) |
| 126 | + |
| 127 | + return lipgloss.JoinVertical(lipgloss.Left, |
| 128 | + titleBar, |
| 129 | + pathRow, |
| 130 | + panels, |
| 131 | + cmdRow, |
| 132 | + status, |
| 133 | + fBar, |
| 134 | + ) |
| 135 | +} |
| 136 | + |
| 137 | +func (m *Model) renderPathBar(idx, w int) string { |
| 138 | + p := &m.panels[idx] |
| 139 | + vfsTag := vfsTagStyle.Render(p.vfs.VFSName()) |
| 140 | + dir := pathStyle.Render(truncatePath(p.currentDir, w-20)) |
| 141 | + branch := "" |
| 142 | + if p.gitBranch != "" { |
| 143 | + branch = " " + branchStyle.Render(" "+p.gitBranch+" ") |
| 144 | + } |
| 145 | + sort := sortTagStyle.Render(p.sortMode.String()) |
| 146 | + return lipgloss.NewStyle().Width(w + 2).Render( |
| 147 | + lipgloss.JoinHorizontal(lipgloss.Top, vfsTag, " ", dir, branch, " ", sort), |
| 148 | + ) |
| 149 | +} |
| 150 | + |
| 151 | +func (m *Model) renderPanel(idx, w int) string { |
| 152 | + p := &m.panels[idx] |
| 153 | + listView := p.fileList.View() |
| 154 | + sel := 0 |
| 155 | + for _, v := range p.selectedFiles { |
| 156 | + if v { |
| 157 | + sel++ |
| 158 | + } |
| 159 | + } |
| 160 | + footer := "" |
| 161 | + if sel > 0 { |
| 162 | + footer = "\n" + warnStyle.Render(fmt.Sprintf(" %d selected", sel)) |
| 163 | + } |
| 164 | + content := listView + footer |
| 165 | + if idx == m.activePanel { |
| 166 | + return activePanelBorder.Width(w).Render(content) |
| 167 | + } |
| 168 | + return inactivePanelBorder.Width(w).Render(content) |
| 169 | +} |
| 170 | + |
| 171 | +func truncatePath(path string, maxLen int) string { |
| 172 | + if len(path) <= maxLen { |
| 173 | + return path |
63 | 174 | } |
64 | | - pwds := lipgloss.JoinHorizontal(lipgloss.Top, leftPwd, rightPwd) |
65 | | - inputView := inputStyle.Render(m.commandInput.View()) |
66 | | - status := m.statusMsg |
67 | | - return lipgloss.JoinVertical(lipgloss.Left, title, pwds, content, inputView, status, fBar, help) |
| 175 | + return "…" + path[len(path)-maxLen+1:] |
68 | 176 | } |
0 commit comments