Skip to content

Commit

Permalink
feat(loader): add styled loader to the status bar
Browse files Browse the repository at this point in the history
Closes #82
  • Loading branch information
AnatolyRugalev committed Sep 21, 2020
1 parent 694002f commit 6c48755
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 10 deletions.
9 changes: 7 additions & 2 deletions app/ui/screen.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ type Screen struct {
status commander.StatusReporter
view commander.View
theme commander.ThemeManager
title *views.BoxLayout
titleBar *views.TextBar
// TODO: separate title widget
title *views.BoxLayout
titleBar *views.TextBar
}

func (s *Screen) View() commander.View {
Expand All @@ -39,6 +40,10 @@ func (s *Screen) Init(status commander.StatusReporter, theme commander.ThemeMana
s.SetTitle(s.title)
}

func (s *Screen) Status() commander.StatusReporter {
return s.status
}

func (s *Screen) UpdateScreen() {
if s.app != nil {
s.app.Update()
Expand Down
85 changes: 85 additions & 0 deletions app/ui/status/loader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package status

import (
"fmt"
"github.com/AnatolyRugalev/kube-commander/commander"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/views"
"go.uber.org/atomic"
"sync"
)

const (
lastPhase = 3
loadingIcon = rune('☸')
)

type loader struct {
views.WidgetWatchers
sync.Mutex
view views.View
screen commander.ScreenHandler

stack *atomic.Int32
phase *atomic.Int32
}

func NewLoader(screen commander.ScreenHandler) *loader {
return &loader{
screen: screen,
stack: atomic.NewInt32(0),
phase: atomic.NewInt32(0),
}
}

func (l *loader) Start() {
l.stack.Inc()
}

func (l *loader) Finish() {
l.stack.Dec()
}

func (l *loader) Tick() {
if !l.phase.CAS(lastPhase, 0) {
l.phase.Inc()
}
if l.stack.Load() != 0 {
l.screen.UpdateScreen()
}
}

func (l *loader) Draw() {
l.Lock()
defer l.Unlock()
if l.view == nil {
return
}
if l.stack.Load() == 0 {
style := l.screen.Theme().GetStyle("status-loader-idle")
l.view.SetContent(0, 0, ' ', nil, style)
l.view.SetContent(1, 0, ' ', nil, style)
} else {
phase := l.phase.Load()
style := l.screen.Theme().GetStyle(fmt.Sprintf("status-loader-phase-%d", phase))
l.view.SetContent(0, 0, loadingIcon, nil, style)
l.view.SetContent(1, 0, ' ', nil, style)
}
}

func (l *loader) Resize() {
}

func (l *loader) HandleEvent(_ tcell.Event) bool {
return false
}

func (l *loader) SetView(view views.View) {
l.Lock()
l.view = view
l.Unlock()
}

func (l *loader) Size() (int, int) {
return 2, 1
}
51 changes: 43 additions & 8 deletions app/ui/status/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,16 @@ import (
)

type Status struct {
views.WidgetWatchers
*focus.Focusable
*views.Text
mu sync.Mutex
mu sync.Mutex

view views.View
loaderV *views.ViewPort
loader *loader
textV *views.ViewPort
text *views.Text

screen commander.ScreenHandler
events chan *tcell.EventKey

Expand All @@ -21,8 +28,33 @@ type Status struct {
clearIn time.Time
}

func (s *Status) LoadingStarted() {
s.loader.Start()
}

func (s *Status) LoadingFinished() {
s.loader.Finish()
}

func (s *Status) Resize() {
w, h := s.view.Size()
s.loaderV.Resize(0, 0, 2, h)
s.textV.Resize(2, 0, w-2, h)
}

func (s *Status) SetView(view views.View) {
s.mu.Lock()
s.view = view
w, h := view.Size()
s.loaderV = views.NewViewPort(view, 0, 0, 2, h)
s.loader.SetView(s.loaderV)
s.textV = views.NewViewPort(view, 2, 0, w-2, h)
s.text.SetView(s.textV)
s.mu.Unlock()
}

func (s *Status) watch() {
ticker := time.NewTicker(time.Millisecond * 100)
ticker := time.NewTicker(time.Millisecond * 200)
for {
t := <-ticker.C
s.mu.Lock()
Expand All @@ -31,6 +63,7 @@ func (s *Status) watch() {
if clear {
s.Clear()
}
s.loader.Tick()
}
}

Expand All @@ -41,8 +74,8 @@ func (s *Status) setMessage(text string, style tcell.Style, clearIn time.Duratio
} else {
s.clearIn = time.Now().Add(clearIn)
}
s.SetText(text)
s.SetStyle(style)
s.text.SetStyle(style)
s.text.SetText(text)
s.mu.Unlock()
s.screen.UpdateScreen()
}
Expand Down Expand Up @@ -87,11 +120,12 @@ func (s *Status) Confirm(msg string) bool {
func (s *Status) Draw() {
s.once.Do(func() {
s.mu.Lock()
s.SetStyle(s.screen.Theme().GetStyle("status-bar"))
s.text.SetStyle(s.screen.Theme().GetStyle("status-bar"))
s.mu.Unlock()
})
s.mu.Lock()
s.Text.Draw()
s.loader.Draw()
s.text.Draw()
s.mu.Unlock()
}

Expand All @@ -102,7 +136,8 @@ func (s *Status) Size() (int, int) {
func NewStatus(screen commander.ScreenHandler) *Status {
s := &Status{
Focusable: focus.NewFocusable(),
Text: views.NewText(),
loader: NewLoader(screen),
text: views.NewText(),
screen: screen,
events: make(chan *tcell.EventKey),
}
Expand Down
24 changes: 24 additions & 0 deletions app/ui/theme/themes/themes.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,29 @@ func BaseStyles() []*pb.Style {
Fg: "confirm-fg",
Bg: "confirm-bg",
},
{
Name: "status-loader-idle",
Bg: "status-bar",
},
{
Name: "status-loader-phase-0",
Bg: "selection-bg",
Fg: "selection-fg",
},
{
Name: "status-loader-phase-1",
Bg: "confirm-bg",
Fg: "confirm-fg",
},
{
Name: "status-loader-phase-2",
Bg: "error-bg",
Fg: "error-fg",
},
{
Name: "status-loader-phase-3",
Bg: "unfocused-bg",
Fg: "unfocused-fg",
},
}
}
3 changes: 3 additions & 0 deletions app/ui/widgets/listTable/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ func (r *ResourceListTable) extractRows(addNamespace bool, event watch.Event) ([
}

func (r *ResourceListTable) loadResourceRows() ([]string, []commander.Row, error) {
r.screen.Status().LoadingStarted()
defer r.screen.Status().LoadingFinished()

namespace := r.container.CurrentNamespace()
table, err := r.container.Client().ListAsTable(context.TODO(), r.resource, namespace)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions commander/screen.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ type StatusReporter interface {
Warning(msg string)
Info(msg string)
Confirm(msg string) bool
LoadingStarted()
LoadingFinished()
}

type ScreenHandler interface {
Status() StatusReporter
UpdateScreen()
Resize()
Theme() ThemeManager
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/mattn/go-runewidth v0.0.9
github.com/spf13/cast v1.3.1
github.com/spf13/cobra v0.0.7
go.uber.org/atomic v1.4.0
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975
google.golang.org/appengine v1.6.1 // indirect
google.golang.org/protobuf v1.25.0
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSf
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
Expand Down

0 comments on commit 6c48755

Please sign in to comment.