apps/sipp/tui/view.go 4.1 K raw
1
package tui
2
3
import (
4
	"fmt"
5
6
	tea "charm.land/bubbletea/v2"
7
	"charm.land/lipgloss/v2"
8
	sharedtui "github.com/stevedylandev/andromeda/pkg/tui"
9
)
10
11
var (
12
	borderStyle    = sharedtui.Border(lipgloss.RoundedBorder())
13
	borderActive   = sharedtui.BorderActive(lipgloss.RoundedBorder())
14
	titleStyle     = sharedtui.TitleStyle
15
	statusOKStyle  = sharedtui.StatusOKStyle
16
	statusErrStyle = sharedtui.StatusErrStyle
17
	hintStyle      = sharedtui.HintStyle
18
	modalStyle       = sharedtui.ModalStyle
19
	statusModalStyle = sharedtui.StatusModalStyle
20
)
21
22
func (m Model) View() tea.View {
23
	if !m.ready {
24
		return tea.View{Content: "loading...", AltScreen: true}
25
	}
26
27
	listW := m.width * 30 / 100
28
	if listW < 24 {
29
		listW = 24
30
	}
31
	contentW := m.width - listW
32
	bodyH := m.height - 1
33
34
	left := m.renderListPane(listW, bodyH)
35
	right := m.renderRightPane(contentW, bodyH)
36
37
	body := lipgloss.JoinHorizontal(lipgloss.Top, left, right)
38
	footer := m.renderFooter()
39
	base := lipgloss.JoinVertical(lipgloss.Left, body, footer)
40
41
	var overlays []*lipgloss.Layer
42
	if m.showHelp {
43
		overlays = append(overlays, centerLayer(m.width, m.height,
44
			modalStyle.Render(m.help.FullHelpView(m.keys.FullHelp())), 1))
45
	}
46
	if m.confirmDelete {
47
		name := ""
48
		if s, ok := m.list.Selected(); ok {
49
			name = s.Name
50
			if name == "" {
51
				name = s.ShortID
52
			}
53
		}
54
		overlays = append(overlays, centerLayer(m.width, m.height,
55
			modalStyle.Render(fmt.Sprintf("Delete %q?\n\ny / n", name)), 2))
56
	}
57
	if m.status != "" {
58
		st := statusOKStyle
59
		if !m.statusOK {
60
			st = statusErrStyle
61
		}
62
		overlays = append(overlays, bottomCenterLayer(m.width, m.height,
63
			statusModalStyle.Render(st.Render(m.status)), 3))
64
	}
65
66
	content := base
67
	if len(overlays) > 0 {
68
		layers := append([]*lipgloss.Layer{lipgloss.NewLayer(base)}, overlays...)
69
		canvas := lipgloss.NewCanvas(m.width, m.height)
70
		canvas.Compose(lipgloss.NewCompositor(layers...))
71
		content = canvas.Render()
72
	}
73
74
	return tea.View{Content: content, AltScreen: true}
75
}
76
77
func (m Model) renderListPane(w, h int) string {
78
	style := borderStyle
79
	if m.state == stateList {
80
		style = borderActive
81
	}
82
	return style.Width(w).Height(h).Render(m.list.View())
83
}
84
85
func (m Model) renderRightPane(w, h int) string {
86
	if m.state == stateForm {
87
		return m.renderForm(w, h)
88
	}
89
	return m.renderContent(w, h)
90
}
91
92
func (m Model) renderContent(w, h int) string {
93
	style := borderStyle
94
	if m.state == stateContent {
95
		style = borderActive
96
	}
97
	header := "preview"
98
	if m.cont.Wrap() {
99
		header += " (wrap)"
100
	}
101
	if t := m.cont.Header(); t != "" {
102
		header = t
103
		if m.cont.Wrap() {
104
			header += " (wrap)"
105
		}
106
	}
107
	inner := lipgloss.JoinVertical(lipgloss.Left, titleStyle.Render(header), m.cont.View())
108
	return style.Width(w).Height(h).Render(inner)
109
}
110
111
func (m Model) renderForm(w, h int) string {
112
	header := "new snippet"
113
	if !m.form.IsCreate() {
114
		header = "edit"
115
	}
116
117
	nameField := m.form.name.View()
118
	if m.form.ActiveField() == formFieldName {
119
		nameField = borderActive.Render(nameField)
120
	} else {
121
		nameField = borderStyle.Render(nameField)
122
	}
123
124
	body := m.form.content.View()
125
	if m.form.ActiveField() == formFieldContent {
126
		body = borderActive.Render(body)
127
	} else {
128
		body = borderStyle.Render(body)
129
	}
130
131
	inner := lipgloss.JoinVertical(lipgloss.Left, titleStyle.Render(header), nameField, body)
132
	return borderActive.Width(w).Height(h).Render(inner)
133
}
134
135
func (m Model) renderFooter() string {
136
	mode := "local"
137
	if m.isRemote {
138
		mode = "remote " + m.backend.RemoteURL()
139
	}
140
	help := m.help.ShortHelpView(m.keys.ShortHelp())
141
	return hintStyle.Render(fmt.Sprintf("[%s] %s", mode, help))
142
}
143
144
func centerLayer(w, h int, content string, z int) *lipgloss.Layer {
145
	cw, ch := lipgloss.Width(content), lipgloss.Height(content)
146
	x := (w - cw) / 2
147
	y := (h - ch) / 2
148
	if x < 0 {
149
		x = 0
150
	}
151
	if y < 0 {
152
		y = 0
153
	}
154
	return lipgloss.NewLayer(content).X(x).Y(y).Z(z)
155
}
156
157
func bottomCenterLayer(w, h int, content string, z int) *lipgloss.Layer {
158
	cw, ch := lipgloss.Width(content), lipgloss.Height(content)
159
	x := (w - cw) / 2
160
	y := h - ch - 1
161
	if x < 0 {
162
		x = 0
163
	}
164
	if y < 0 {
165
		y = 0
166
	}
167
	return lipgloss.NewLayer(content).X(x).Y(y).Z(z)
168
}
169
170
func paneFrameWidth() int  { return borderStyle.GetHorizontalFrameSize() }
171
func paneFrameHeight() int { return borderStyle.GetVerticalFrameSize() }