Skip to content

Commit

Permalink
- Add model stack
Browse files Browse the repository at this point in the history
- Refactor pages struct
  • Loading branch information
RamanaReddy0M committed Jan 31, 2023
1 parent 9054e5b commit d60f983
Show file tree
Hide file tree
Showing 3 changed files with 306 additions and 2 deletions.
190 changes: 190 additions & 0 deletions internal/model/stack.go
@@ -0,0 +1,190 @@
package model

import (
"sync"

"github.com/rs/zerolog/log"
)

const (
// StackPush denotes an add on the stack.
StackPush StackAction = 1 << iota

// StackPop denotes a delete on the stack.
StackPop
)

// StackAction represents an action on the stack.
type StackAction int

// StackEvent represents an operation on a view stack.
type StackEvent struct {
// Kind represents the event condition.
Action StackAction

// Item represents the targeted item.
Component Component
}

// StackListener represents a stack listener.
type StackListener interface {
// StackPushed indicates a new item was added.
StackPushed(Component)

// StackPopped indicates an item was deleted
StackPopped(old, new Component)

// StackTop indicates the top of the stack
StackTop(Component)
}

// Stack represents a stacks of components.
type Stack struct {
components []Component
listeners []StackListener
mx sync.RWMutex
}

// NewStack returns a new initialized stack.
func NewStack() *Stack {
return &Stack{}
}

// Flatten returns a string representation of the component stack.
func (s *Stack) Flatten() []string {
s.mx.RLock()
defer s.mx.RUnlock()

ss := make([]string, len(s.components))
for i, c := range s.components {
ss[i] = c.Name()
}
return ss
}

// RemoveListener removes a listener.
func (s *Stack) RemoveListener(l StackListener) {
victim := -1
for i, lis := range s.listeners {
if lis == l {
victim = i
break
}
}
if victim == -1 {
return
}
s.listeners = append(s.listeners[:victim], s.listeners[victim+1:]...)
}

// AddListener registers a stack listener.
func (s *Stack) AddListener(l StackListener) {
s.listeners = append(s.listeners, l)
if !s.Empty() {
l.StackTop(s.Top())
}
}

// Push adds a new item.
func (s *Stack) Push(c Component) {
if top := s.Top(); top != nil {
//top.Stop()
}

s.mx.Lock()
{
s.components = append(s.components, c)
}
s.mx.Unlock()
s.notify(StackPush, c)
}

// Pop removed the top item and returns it.
func (s *Stack) Pop() (Component, bool) {
if s.Empty() {
return nil, false
}

var c Component
s.mx.Lock()
{
c = s.components[len(s.components)-1]
s.components = s.components[:len(s.components)-1]
}
s.mx.Unlock()
s.notify(StackPop, c)

return c, true
}

// Peek returns stack state.
func (s *Stack) Peek() []Component {
s.mx.RLock()
defer s.mx.RUnlock()

return s.components
}

// Clear clear out the stack using pops.
func (s *Stack) Clear() {
for range s.components {
s.Pop()
}
}

// Empty returns true if the stack is empty.
func (s *Stack) Empty() bool {
s.mx.RLock()
defer s.mx.RUnlock()

return len(s.components) == 0
}

// IsLast indicates if stack only has one item left.
func (s *Stack) IsLast() bool {
return len(s.components) == 1
}

// Previous returns the previous component if any.
func (s *Stack) Previous() Component {
if s.Empty() {
return nil
}
if s.IsLast() {
return s.Top()
}

return s.components[len(s.components)-2]
}

// Top returns the top most item or nil if the stack is empty.
func (s *Stack) Top() Component {
if s.Empty() {
return nil
}

return s.components[len(s.components)-1]
}

func (s *Stack) notify(a StackAction, c Component) {
for _, l := range s.listeners {
switch a {
case StackPush:
l.StackPushed(c)
case StackPop:
l.StackPopped(c, s.Top())
}
}
}

// ----------------------------------------------------------------------------
// Helpers...

// Dump prints out the stack.
func (s *Stack) Dump() {
log.Debug().Msgf("--- Stack Dump %p---", s)
for i, c := range s.components {
log.Debug().Msgf("%d -- %s -- %#v", i, c.Name(), c)
}
log.Debug().Msg("------------------")
}
23 changes: 23 additions & 0 deletions internal/model/types.go
@@ -0,0 +1,23 @@
package model

import "github.com/derailed/tview"

// Hinter represent a menu mnemonic provider.
type Hinter interface {
// Hints returns a collection of menu hints.
Hints() MenuHints
}

// Primitive represents a UI primitive.
type Primitive interface {
tview.Primitive

// Name returns the view name.
Name() string
}

// Component represents a ui component.
type Component interface {
Primitive
Hinter
}
95 changes: 93 additions & 2 deletions internal/ui/pages.go
@@ -1,14 +1,105 @@
package ui

import (
"fmt"

"github.com/one2nc/cloud-lens/internal/model"
"github.com/derailed/tview"
"github.com/rs/zerolog/log"
)

// Pages represents a stack of view pages.
type Pages struct {
*tview.Pages
*model.Stack
}

// NewPages return a new view.
func NewPages() *Pages {
pages := Pages{Pages: tview.NewPages()}
return &pages
p := Pages{
Pages: tview.NewPages(),
Stack: model.NewStack(),
}
p.Stack.AddListener(&p)

return &p
}

// IsTopDialog checks if front page is a dialog.
func (p *Pages) IsTopDialog() bool {
_, pa := p.GetFrontPage()
switch pa.(type) {
case *tview.ModalForm:
return true
default:
return false
}
}

// Show displays a given page.
func (p *Pages) Show(c model.Component) {
p.SwitchToPage(componentID(c))
}

// Current returns the current component.
func (p *Pages) Current() model.Component {
c := p.CurrentPage()
if c == nil {
return nil
}

return c.Item.(model.Component)
}

// AddAndShow adds a new page and bring it to front.
func (p *Pages) addAndShow(c model.Component) {
p.add(c)
p.Show(c)
}

// Add adds a new page.
func (p *Pages) add(c model.Component) {
p.AddPage(componentID(c), c, true, true)
}

// Delete removes a page.
func (p *Pages) delete(c model.Component) {
p.RemovePage(componentID(c))
}

// Dump for debug.
func (p *Pages) Dump() {
log.Debug().Msgf("Dumping Pages %p", p)
for i, c := range p.Stack.Peek() {
log.Debug().Msgf("%d -- %s -- %#v", i, componentID(c), p.GetPrimitive(componentID(c)))
}
}

// Stack Protocol...

// StackPushed notifies a new component was pushed.
func (p *Pages) StackPushed(c model.Component) {
p.addAndShow(c)
}

// StackPopped notifies a component was removed.
func (p *Pages) StackPopped(o, top model.Component) {
p.delete(o)
}

// StackTop notifies a new component is at the top of the stack.
func (p *Pages) StackTop(top model.Component) {
if top == nil {
return
}
p.Show(top)
}

// Helpers...

func componentID(c model.Component) string {
if c.Name() == "" {
panic("Component has no name")
}
return fmt.Sprintf("%s-%p", c.Name(), c)
}

0 comments on commit d60f983

Please sign in to comment.