Skip to content

Commit 5107929

Browse files
committed
feat(harmonize color on flash banner + add filter not operation ^)
1 parent 35db462 commit 5107929

17 files changed

Lines changed: 198 additions & 325 deletions

File tree

internal/ui/app.go

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ type App struct {
5757

5858
flashMx sync.Mutex
5959
flashExpiry time.Time
60+
61+
appendTimer *time.Timer
62+
appendMx sync.Mutex
6063
}
6164

6265
// Ensure App implements AppController interface
@@ -425,17 +428,6 @@ func (a *App) SetFilter(filter string) {
425428
a.ActiveFilter = filter
426429
}
427430

428-
func (a *App) SetFlashText(text string) {
429-
a.Flash.SetText(text)
430-
}
431-
432-
func (a *App) AppendFlash(text string) {
433-
a.Flash.Append(text)
434-
435-
// Optional: Auto-clear the appended part after delay?
436-
// For now let's keep it simple, it will be cleared on next full refresh
437-
}
438-
439431
func (a *App) RestoreFocus() {
440432
page, _ := a.Pages.GetFrontPage()
441433
if view, ok := a.Views[page]; ok {
@@ -548,6 +540,46 @@ func (a *App) IsPaused() bool {
548540
return a.paused
549541
}
550542

543+
544+
func (a *App) SetFlashText(text string) {
545+
a.flashMx.Lock()
546+
a.flashExpiry = time.Now().Add(100 * time.Millisecond) // Short lock to prevent immediate overwrite
547+
a.flashMx.Unlock()
548+
a.Flash.SetText(text)
549+
}
550+
551+
func (a *App) AppendFlash(text string) {
552+
// No need to lock main flash, as we use a separate slot in FlashComponent.
553+
a.appendMx.Lock()
554+
defer a.appendMx.Unlock()
555+
556+
if a.appendTimer != nil {
557+
a.appendTimer.Stop()
558+
}
559+
560+
a.Flash.Append(text)
561+
562+
a.appendTimer = time.AfterFunc(2*time.Second, func() {
563+
a.appendMx.Lock()
564+
defer a.appendMx.Unlock()
565+
a.Flash.ClearAppend()
566+
// SafeQueueUpdateDraw handles thread safety for the draw
567+
a.SafeQueueUpdateDraw(func() {})
568+
})
569+
}
570+
571+
func (a *App) AppendFlashError(text string) {
572+
a.AppendFlash(fmt.Sprintf("[black:red] <error: %s> [-:-]", text))
573+
}
574+
575+
func (a *App) AppendFlashPending(text string) {
576+
a.AppendFlash(fmt.Sprintf("[black:%s] <pending: %s> [-:-]", styles.ColorIdle, text))
577+
}
578+
579+
func (a *App) AppendFlashSuccess(text string) {
580+
a.AppendFlash(fmt.Sprintf("[black:#50fa7b] <success: %s> [-:-]", text))
581+
}
582+
551583
func (a *App) SetFlashMessage(text string, duration time.Duration) {
552584
a.flashMx.Lock()
553585
a.flashExpiry = time.Now().Add(duration)
@@ -556,6 +588,18 @@ func (a *App) SetFlashMessage(text string, duration time.Duration) {
556588
a.Flash.SetText(text)
557589
}
558590

591+
func (a *App) SetFlashError(text string) {
592+
a.AppendFlash(fmt.Sprintf("[black:red] <error: %s> [-:-]", text))
593+
}
594+
595+
func (a *App) SetFlashPending(text string) {
596+
a.AppendFlash(fmt.Sprintf("[black:%s] <pending: %s> [-:-]", styles.ColorIdle, text))
597+
}
598+
599+
func (a *App) SetFlashSuccess(text string) {
600+
a.AppendFlash(fmt.Sprintf("[black:#50fa7b] <success: %s> [-:-]", text))
601+
}
602+
559603
func (a *App) IsFlashLocked() bool {
560604
a.flashMx.Lock()
561605
defer a.flashMx.Unlock()

internal/ui/app_actions.go

Lines changed: 5 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"github.com/jr-k/d4s/internal/ui/common"
1111
"github.com/jr-k/d4s/internal/ui/components/view"
1212
"github.com/jr-k/d4s/internal/ui/dialogs"
13-
"github.com/jr-k/d4s/internal/ui/styles"
1413
)
1514

1615
func (a *App) PerformAction(action func(id string) error, actionName string, color tcell.Color) {
@@ -37,7 +36,7 @@ func (a *App) PerformAction(action func(id string) error, actionName string, col
3736
if len(ids) == 1 {
3837
plural = ""
3938
}
40-
a.SetFlashMessage(fmt.Sprintf(" [red]%s %d item%s...", actionName, len(ids), plural), 10*time.Minute)
39+
a.AppendFlashPending(fmt.Sprintf("%s %d item%s...", actionName, len(ids), plural))
4140

4241
a.RunInBackground(func() {
4342
var errs []string
@@ -58,15 +57,15 @@ func (a *App) PerformAction(action func(id string) error, actionName string, col
5857
a.StartAutoRefresh()
5958

6059
if len(errs) > 0 {
61-
a.SetFlashMessage(fmt.Sprintf(" [red]%s completed with errors", actionName), 3*time.Second)
60+
a.AppendFlashError(fmt.Sprintf("%s completed with errors", actionName))
6261
dialogs.ShowResultModal(a, actionName, len(ids)-len(errs), errs)
6362
} else {
6463
plural := "s"
6564
if len(ids) == 1 {
6665
plural = ""
6766
}
6867
// Show success message for 3 seconds
69-
a.SetFlashMessage(fmt.Sprintf(" [#50fa7b]%s %d item%s done", actionName, len(ids), plural), 1*time.Second)
68+
a.AppendFlashSuccess(fmt.Sprintf("%s %d item%s done", actionName, len(ids), plural))
7069

7170
// Clear selection on success?
7271
view.SelectedIDs = make(map[string]bool)
@@ -118,78 +117,6 @@ func (a *App) getSelectedID(v *view.ResourceView) (string, error) {
118117
return v.Data[dataIndex].GetID(), nil
119118
}
120119

121-
func (a *App) PerformDelete() {
122-
page, _ := a.Pages.GetFrontPage()
123-
view, ok := a.Views[page]
124-
125-
if !ok || view.RemoveFunc == nil {
126-
return
127-
}
128-
129-
action := view.RemoveFunc
130-
131-
ids, err := a.getTargetIDs(view)
132-
if err != nil {
133-
return
134-
}
135-
136-
label := ids[0]
137-
if len(ids) == 1 {
138-
row, _ := view.Table.GetSelection()
139-
if row > 0 && row <= len(view.Data) {
140-
item := view.Data[row-1]
141-
if item.GetID() == ids[0] {
142-
cells := item.GetCells()
143-
if len(cells) > 1 {
144-
label = fmt.Sprintf("%s ([#00ffff]%s[yellow])", label, cells[1])
145-
}
146-
}
147-
}
148-
} else if len(ids) > 1 {
149-
label = fmt.Sprintf("%d items", len(ids))
150-
}
151-
152-
dialogs.ShowConfirmation(a, "DELETE", label, func(force bool) {
153-
simpleAction := func(id string) error {
154-
return action(id, force, a)
155-
}
156-
// view.HighlightIDs(ids, styles.ColorStatusRed, styles.ColorStatusRedDarkBg, time.Second)
157-
// view.DeferRefresh(time.Second)
158-
a.PerformAction(simpleAction, "Deleting", styles.ColorStatusRed)
159-
})
160-
a.UpdateShortcuts()
161-
}
162-
163-
func (a *App) PerformPrune() {
164-
page, _ := a.Pages.GetFrontPage()
165-
view, ok := a.Views[page]
166-
167-
if !ok || view.PruneFunc == nil {
168-
a.Flash.SetText(fmt.Sprintf("[yellow]Prune not available for %s", page))
169-
return
170-
}
171-
172-
action := view.PruneFunc
173-
// Capitalize page name for display (e.g. "images" -> "Images")
174-
name := strings.Title(page)
175-
176-
dialogs.ShowConfirmation(a, "PRUNE", name, func(force bool) {
177-
a.Flash.SetText(fmt.Sprintf("[yellow]Pruning %s...", name))
178-
a.RunInBackground(func() {
179-
err := action(a)
180-
a.TviewApp.QueueUpdateDraw(func() {
181-
if err != nil {
182-
a.Flash.SetText(fmt.Sprintf("[red]Prune Error: %v", err))
183-
} else {
184-
a.Flash.SetText(fmt.Sprintf("[green]Pruned %s", name))
185-
a.RefreshCurrentView()
186-
}
187-
})
188-
})
189-
})
190-
a.UpdateShortcuts()
191-
}
192-
193120
func (a *App) PerformCopy() {
194121
page, _ := a.Pages.GetFrontPage()
195122
view, ok := a.Views[page]
@@ -253,13 +180,13 @@ func (a *App) PerformCopy() {
253180

254181
// 5. Copy
255182
if err := a.CopyToClipboard(value); err != nil {
256-
a.AppendFlash(fmt.Sprintf("[red]Copy error: %v", err))
183+
a.AppendFlashError(fmt.Sprintf("%v", err))
257184
} else {
258185
preview := value
259186
if len(preview) > 60 {
260187
preview = preview[:60] + "..."
261188
}
262-
a.AppendFlash(fmt.Sprintf("[black:#50fa7b] <copied: %s>[-]", preview))
189+
a.AppendFlashSuccess(fmt.Sprintf("copied %s", preview))
263190
}
264191
a.UpdateShortcuts()
265192
}

internal/ui/common/common.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,14 @@ type AppController interface {
3838
SetActiveScope(scope *Scope)
3939
SetFilter(filter string)
4040
SetFlashText(text string)
41+
SetFlashMessage(text string, duration time.Duration)
42+
SetFlashError(text string)
43+
SetFlashPending(text string)
44+
SetFlashSuccess(text string)
4145
AppendFlash(text string)
46+
AppendFlashError(text string)
47+
AppendFlashPending(text string)
48+
AppendFlashSuccess(text string)
4249
RestoreFocus()
4350

4451
// Direct access for command component (needed for handlers)

internal/ui/components/command/command.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -217,17 +217,6 @@ func (c *CommandComponent) setupHandlers() {
217217

218218
// Apply Filter (even if empty, to clear it)
219219
c.App.SetActiveFilter(filter)
220-
221-
// Flash Message Context
222-
// msg := ""
223-
// if filter != "" {
224-
// msg = fmt.Sprintf("Filter: %s", filter)
225-
// front, _ := c.App.GetPages().GetFrontPage()
226-
// if front == "inspect" {
227-
// msg = fmt.Sprintf("Search: %s", filter)
228-
// }
229-
// }
230-
// c.App.SetFlashText(msg)
231220

232221
} else {
233222
// Command Mode
Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package footer
22

33
import (
4-
"strings"
5-
64
"github.com/jr-k/d4s/internal/ui/styles"
75
"github.com/rivo/tview"
86
)
97

108
type FlashComponent struct {
11-
View *tview.TextView
9+
View *tview.TextView
10+
mainText string
11+
appendedText string
1212
}
1313

1414
func NewFlashComponent() *FlashComponent {
@@ -18,27 +18,28 @@ func NewFlashComponent() *FlashComponent {
1818
}
1919

2020
func (f *FlashComponent) SetText(text string) {
21-
f.View.SetText(text)
21+
f.mainText = text
22+
f.render()
2223
}
2324

24-
// Appends text to existing content temporarily, replacing any existing "copied" message
25+
// Appends text to existing content temporarily.
26+
// It uses a dedicated slot, so multiple appends overwrite each other (last one wins),
27+
// but it stays separate from the main text (breadcrumb/status).
2528
func (f *FlashComponent) Append(text string) {
26-
current := f.View.GetText(false)
27-
28-
// Remove existing copy notifications to prevent stacking
29-
// Looking for patterns like " <copied: ...> "
30-
// A simple but effective way is to split by " <copied:" and take the first part
31-
// Or use a more robust regex if needed, but string manipulation is faster here
32-
// Assuming the tag starts with " [black:#50fa7b:b] <copied:" and ends with "[-] "
33-
34-
// Strategy: Split by the start of our known copy tag style
35-
// Tag format from app_actions: [black:#50fa7b] <copied:
36-
37-
const tagStart = " [black:#50fa7b] <copied:"
38-
39-
if idx := strings.Index(current, tagStart); idx != -1 {
40-
current = current[:idx]
29+
f.appendedText = text
30+
f.render()
31+
}
32+
33+
func (f *FlashComponent) ClearAppend() {
34+
f.appendedText = ""
35+
f.render()
36+
}
37+
38+
func (f *FlashComponent) render() {
39+
full := f.mainText
40+
if f.appendedText != "" {
41+
// Ensure separation
42+
full += " " + f.appendedText
4143
}
42-
43-
f.View.SetText(current + " " + text)
44+
f.View.SetText(full)
4445
}

internal/ui/components/inspect/log_inspector.go

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ func (i *LogInspector) startStreaming() {
270270

271271
if i.TextView != nil {
272272
i.TextView.Clear()
273-
i.TextView.SetText("[yellow]Loading logs...\n")
273+
i.TextView.SetText(" [orange]Loading logs...\n")
274274
}
275275

276276
// Channels for buffering
@@ -385,13 +385,32 @@ func (i *LogInspector) startStreaming() {
385385
return
386386
}
387387

388-
// Filter logic
388+
// Filter logic (supports negation with ^)
389389
if i.filter != "" {
390-
if !strings.Contains(line, i.filter) {
391-
continue
390+
filterTerm := i.filter
391+
negate := false
392+
if strings.HasPrefix(filterTerm, "^") {
393+
negate = true
394+
filterTerm = strings.TrimPrefix(filterTerm, "^")
395+
}
396+
397+
// If negate and term is empty (input is "^"), treat as match all (show all)
398+
if negate && filterTerm == "" {
399+
// no-op, show line
400+
} else {
401+
contains := strings.Contains(line, filterTerm)
402+
if negate {
403+
if contains {
404+
continue
405+
}
406+
} else {
407+
if !contains {
408+
continue
409+
}
410+
// Highlight for positive match only
411+
line = strings.ReplaceAll(line, filterTerm, fmt.Sprintf("[yellow]%s[-]", filterTerm))
412+
}
392413
}
393-
// Highlight
394-
line = strings.ReplaceAll(line, i.filter, fmt.Sprintf("[yellow]%s[-]", i.filter))
395414
}
396415

397416
// Timestamp Coloring

internal/ui/components/inspect/text_viewer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,9 @@ func (t *TextViewer) highlightContent(content, lang string) string {
167167

168168
func (t *TextViewer) copyToClipboard() {
169169
if err := clipboard.WriteAll(t.content); err != nil {
170-
t.App.SetFlashText(fmt.Sprintf("[red]Copy error: %v", err))
170+
t.App.AppendFlashError(fmt.Sprintf("%v", err))
171171
} else {
172-
t.App.SetFlashText(fmt.Sprintf(" [black:#50fa7b] <copied: %d bytes>[-] ", len(t.content)))
172+
t.App.AppendFlashSuccess(fmt.Sprintf("copied %d bytes", len(t.content)))
173173
}
174174
}
175175

0 commit comments

Comments
 (0)