v0.20.0 c741a39

purge.go RawHistoryFolder

1package model
2
3import (
4	"context"
5	"fmt"
6
7	tea "github.com/charmbracelet/bubbletea"
8	"github.com/charmbracelet/lipgloss"
9
10	"github.com/bnema/dumber/internal/application/usecase"
11	"github.com/bnema/dumber/internal/cli/styles"
12	"github.com/bnema/dumber/internal/domain/entity"
13)
14
15// PurgeModel wraps styles.PurgeModel for standalone CLI use.
16type PurgeModel struct {
17	selector styles.PurgeModel
18	purgeUC  *usecase.PurgeDataUseCase
19
20	loading bool
21	purging bool
22	done    bool
23
24	results *usecase.PurgeOutput
25	info    string
26	err     error
27
28	theme *styles.Theme
29}
30
31// NewPurgeModel creates a new purge command model.
32func NewPurgeModel(theme *styles.Theme, purgeUC *usecase.PurgeDataUseCase) PurgeModel {
33	return PurgeModel{theme: theme, purgeUC: purgeUC, loading: true}
34}
35
36type purgeTargetsLoadedMsg struct {
37	targets []entity.PurgeTarget
38	err     error
39}
40
41type purgeCompleteMsg struct {
42	out *usecase.PurgeOutput
43	err error
44}
45
46// Init implements tea.Model.
47func (m PurgeModel) Init() tea.Cmd {
48	return m.loadTargets()
49}
50
51func (m PurgeModel) loadTargets() tea.Cmd {
52	return func() tea.Msg {
53		targets, err := m.purgeUC.GetPurgeTargets(context.Background())
54		return purgeTargetsLoadedMsg{targets: targets, err: err}
55	}
56}
57
58// Update implements tea.Model.
59func (m PurgeModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
60	switch msg := msg.(type) {
61	case purgeTargetsLoadedMsg:
62		m.loading = false
63		if msg.err != nil {
64			m.err = msg.err
65			m.done = true
66			return m, nil
67		}
68
69		m.selector = styles.NewPurge(m.theme, msg.targets)
70		return m, nil
71
72	case purgeCompleteMsg:
73		m.purging = false
74		m.done = true
75		m.results = msg.out
76		if msg.err != nil {
77			m.err = msg.err
78		}
79		return m, nil
80
81	case tea.KeyMsg:
82		if m.done {
83			return m, tea.Quit
84		}
85		if m.loading || m.purging {
86			return m, nil
87		}
88	}
89
90	if m.loading || m.purging {
91		return m, nil
92	}
93
94	selector, cmd := m.selector.Update(msg)
95	m.selector = selector
96
97	if m.selector.Done() {
98		if m.selector.Canceled {
99			return m, tea.Quit
100		}
101		if m.selector.Confirmed {
102			targetTypes := m.selector.SelectedTypes()
103			if len(targetTypes) == 0 {
104				m.done = true
105				m.info = "Nothing selected"
106				return m, nil
107			}
108			m.purging = true
109			return m, m.performPurge(targetTypes)
110		}
111	}
112
113	return m, cmd
114}
115
116func (m PurgeModel) performPurge(targetTypes []entity.PurgeTargetType) tea.Cmd {
117	return func() tea.Msg {
118		out, err := m.purgeUC.Execute(context.Background(), usecase.PurgeInput{TargetTypes: targetTypes})
119		return purgeCompleteMsg{out: out, err: err}
120	}
121}
122
123// View implements tea.Model.
124func (m PurgeModel) View() string {
125	t := m.theme
126
127	if m.loading {
128		spinner := styles.NewLoading(t, "Scanning purge targets...")
129		return t.Box.Render(spinner.View())
130	}
131
132	if m.purging {
133		spinner := styles.NewLoading(t, "Purging...")
134		return t.Box.Render(spinner.View())
135	}
136
137	if m.done {
138		if m.info != "" {
139			content := lipgloss.JoinVertical(
140				lipgloss.Left,
141				t.Subtle.Render(m.info),
142				"",
143				t.Subtle.Render("Press any key to exit"),
144			)
145			return t.Box.Render(content)
146		}
147
148		if m.err != nil && m.results == nil {
149			content := lipgloss.JoinVertical(
150				lipgloss.Left,
151				t.ErrorStyle.Render("Error: "+m.err.Error()),
152				"",
153				t.Subtle.Render("Press any key to exit"),
154			)
155			return t.Box.Render(content)
156		}
157
158		contentLines := []string{t.Title.Render("Purge complete")}
159		if m.results != nil {
160			for _, r := range m.results.Results {
161				if r.Success {
162					contentLines = append(contentLines, fmt.Sprintf("%s %s", t.SuccessStyle.Render(styles.IconCheck), r.Target.Path))
163				} else {
164					contentLines = append(contentLines, fmt.Sprintf("%s %s: %v", t.ErrorStyle.Render(styles.IconX), r.Target.Path, r.Error))
165				}
166			}
167			contentLines = append(
168				contentLines,
169				"",
170				t.Subtle.Render(
171					fmt.Sprintf("%d succeeded, %d failed", m.results.SuccessCount, m.results.FailureCount),
172				),
173			)
174		}
175		contentLines = append(contentLines, "", t.Subtle.Render("Press any key to exit"))
176		return t.Box.Render(lipgloss.JoinVertical(lipgloss.Left, contentLines...))
177	}
178
179	return m.selector.View()
180}
181
182var _ tea.Model = (*PurgeModel)(nil)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close