257 lines
5.3 KiB
Go
257 lines
5.3 KiB
Go
package tui
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"charm.land/bubbles/v2/textinput"
|
|
tea "charm.land/bubbletea/v2"
|
|
"charm.land/lipgloss/v2"
|
|
|
|
"dirmd/internal/config"
|
|
)
|
|
|
|
type State int
|
|
|
|
const (
|
|
StateForm State = iota
|
|
StateGenerating
|
|
StateDone
|
|
StateError
|
|
)
|
|
|
|
type genResultMsg struct {
|
|
result GenerateResult
|
|
}
|
|
|
|
type spinnerState struct {
|
|
frame int
|
|
}
|
|
|
|
func (s *spinnerState) View() string {
|
|
frames := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
|
|
return frames[s.frame%len(frames)]
|
|
}
|
|
|
|
type Model struct {
|
|
state State
|
|
form FormModel
|
|
result GenerateResult
|
|
spinner spinnerState
|
|
isDark bool
|
|
width int
|
|
height int
|
|
command string
|
|
}
|
|
|
|
func New() Model {
|
|
return Model{
|
|
state: StateForm,
|
|
form: NewForm(),
|
|
isDark: true,
|
|
}
|
|
}
|
|
|
|
func (m Model) Init() tea.Cmd {
|
|
return textinput.Blink
|
|
}
|
|
|
|
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
switch msg := msg.(type) {
|
|
case tea.BackgroundColorMsg:
|
|
m.isDark = msg.IsDark()
|
|
m.form.SetDark(m.isDark)
|
|
return m, nil
|
|
|
|
case tea.WindowSizeMsg:
|
|
m.width = msg.Width
|
|
m.height = msg.Height
|
|
m.form.SetSize(msg.Width, msg.Height)
|
|
return m, nil
|
|
|
|
case tea.KeyPressMsg:
|
|
if isQuit(msg) && m.state != StateGenerating {
|
|
return m, tea.Quit
|
|
}
|
|
|
|
switch m.state {
|
|
case StateForm:
|
|
if isSubmit(msg) && m.form.focusIndex == fieldGenerateBtn {
|
|
cfg, err := m.form.ToConfig()
|
|
if err != nil {
|
|
m.form.SetError(err.Error())
|
|
return m, nil
|
|
}
|
|
m.command = buildCommand(cfg)
|
|
m.state = StateGenerating
|
|
return m, func() tea.Msg {
|
|
return genResultMsg{result: Generate(cfg)}
|
|
}
|
|
}
|
|
if isSubmit(msg) && m.form.focusIndex == fieldCancelBtn {
|
|
return m, tea.Quit
|
|
}
|
|
var cmd tea.Cmd
|
|
m.form, cmd = m.form.Update(msg)
|
|
return m, cmd
|
|
|
|
case StateDone, StateError:
|
|
return m, tea.Quit
|
|
}
|
|
|
|
case genResultMsg:
|
|
m.result = msg.result
|
|
if m.result.Error != nil {
|
|
m.state = StateError
|
|
} else {
|
|
m.state = StateDone
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
if m.state == StateForm {
|
|
var cmd tea.Cmd
|
|
m.form, cmd = m.form.Update(msg)
|
|
return m, cmd
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
func buildCommand(cfg *config.Config) string {
|
|
var parts []string
|
|
parts = append(parts, "dirmd")
|
|
|
|
parts = append(parts, "-o", cfg.OutputPath)
|
|
|
|
if cfg.AppendMode {
|
|
parts = append(parts, "-a")
|
|
}
|
|
if cfg.Force {
|
|
parts = append(parts, "-f")
|
|
}
|
|
if cfg.VerticalSlices {
|
|
parts = append(parts, "--vertical-slices")
|
|
}
|
|
if cfg.SkipFrontend {
|
|
parts = append(parts, "--skip-frontend")
|
|
}
|
|
if cfg.FrontendOnly {
|
|
parts = append(parts, "--frontend-only")
|
|
}
|
|
if cfg.OmitTest {
|
|
parts = append(parts, "--omit-test")
|
|
}
|
|
if cfg.OmitMd {
|
|
parts = append(parts, "--omit-md")
|
|
}
|
|
if cfg.ProtonDrive {
|
|
parts = append(parts, "--proton-drive")
|
|
if cfg.DrivePath != "" {
|
|
parts = append(parts, "--drive-path", cfg.DrivePath)
|
|
}
|
|
}
|
|
if cfg.MaxDepth != 20 {
|
|
parts = append(parts, "--max-depth", fmt.Sprintf("%d", cfg.MaxDepth))
|
|
}
|
|
if cfg.MaxSize != 512*1024 {
|
|
parts = append(parts, "--max-size", fmt.Sprintf("%d", cfg.MaxSize))
|
|
}
|
|
|
|
extraIgnores := extraIgnores(cfg)
|
|
for _, ig := range extraIgnores {
|
|
parts = append(parts, "--ignore", ig)
|
|
}
|
|
|
|
parts = append(parts, cfg.InputPath)
|
|
|
|
return strings.Join(parts, " ")
|
|
}
|
|
|
|
func extraIgnores(cfg *config.Config) []string {
|
|
defaultSet := make(map[string]bool)
|
|
for _, d := range config.DefaultIgnores {
|
|
defaultSet[d] = true
|
|
}
|
|
var extra []string
|
|
for _, ig := range cfg.Ignores {
|
|
if !defaultSet[ig] {
|
|
extra = append(extra, ig)
|
|
}
|
|
}
|
|
return extra
|
|
}
|
|
|
|
func (m Model) View() tea.View {
|
|
var content string
|
|
|
|
switch m.state {
|
|
case StateForm:
|
|
content = m.form.View()
|
|
case StateGenerating:
|
|
content = m.renderGenerating()
|
|
case StateDone:
|
|
content = m.renderDone()
|
|
case StateError:
|
|
content = m.renderError()
|
|
}
|
|
|
|
v := tea.NewView(content)
|
|
v.AltScreen = true
|
|
return v
|
|
}
|
|
|
|
func (m Model) renderGenerating() string {
|
|
var b strings.Builder
|
|
b.WriteString(titleStyle.Render("dirmd"))
|
|
b.WriteString("\n\n")
|
|
b.WriteString(fmt.Sprintf(" %s Generating...\n", m.spinner.View()))
|
|
b.WriteString("\n")
|
|
b.WriteString(footerStyle.Render("Please wait."))
|
|
|
|
content := appBorder.Render(b.String())
|
|
if m.width > 0 && m.height > 0 {
|
|
return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, content)
|
|
}
|
|
return content
|
|
}
|
|
|
|
func (m Model) renderDone() string {
|
|
var b strings.Builder
|
|
b.WriteString(titleStyle.Render("dirmd"))
|
|
b.WriteString("\n\n")
|
|
b.WriteString(successStyle.Render("✓ Done"))
|
|
b.WriteString("\n\n")
|
|
for _, f := range m.result.Files {
|
|
b.WriteString(fmt.Sprintf(" %s\n", f))
|
|
}
|
|
if m.result.Uploaded {
|
|
b.WriteString(successStyle.Render("\n Uploaded to Proton Drive"))
|
|
}
|
|
b.WriteString("\n\n")
|
|
b.WriteString(footerStyle.Render("Press any key to exit."))
|
|
|
|
content := appBorder.Render(b.String())
|
|
if m.width > 0 && m.height > 0 {
|
|
return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, content)
|
|
}
|
|
return content
|
|
}
|
|
|
|
func (m Model) renderError() string {
|
|
var b strings.Builder
|
|
b.WriteString(titleStyle.Render("dirmd"))
|
|
b.WriteString("\n\n")
|
|
b.WriteString(errorStyle.Render("✗ Error"))
|
|
b.WriteString("\n\n")
|
|
b.WriteString(fmt.Sprintf(" %s\n", m.result.Error.Error()))
|
|
b.WriteString("\n")
|
|
b.WriteString(footerStyle.Render("Press any key to exit."))
|
|
|
|
content := appBorder.Render(b.String())
|
|
if m.width > 0 && m.height > 0 {
|
|
return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, content)
|
|
}
|
|
return content
|
|
}
|