414 lines
7.5 KiB
Go
414 lines
7.5 KiB
Go
package state
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
|
||
|
|
"github.com/StevanFreeborn/term-tier/internal/core"
|
||
|
|
"github.com/StevanFreeborn/term-tier/internal/styles"
|
||
|
|
"github.com/charmbracelet/bubbles/textinput"
|
||
|
|
tea "github.com/charmbracelet/bubbletea"
|
||
|
|
)
|
||
|
|
|
||
|
|
type Mode int
|
||
|
|
|
||
|
|
const (
|
||
|
|
NavigationMode Mode = iota
|
||
|
|
InputMode
|
||
|
|
HelpMode
|
||
|
|
)
|
||
|
|
|
||
|
|
type State interface {
|
||
|
|
Mode() Mode
|
||
|
|
Tiers() []core.Tier
|
||
|
|
CursorRow() int
|
||
|
|
CursorCol() int
|
||
|
|
Dragging() bool
|
||
|
|
SetStatus(msg string)
|
||
|
|
StatusMsg() string
|
||
|
|
WindowWidth() int
|
||
|
|
WindowHeight() int
|
||
|
|
SetWindowSize(width int, height int)
|
||
|
|
InputTitle() string
|
||
|
|
Input() textinput.Model
|
||
|
|
UpdateInput(msg tea.Msg) tea.Cmd
|
||
|
|
StartInputMode(title string, callback func(string, State) tea.Cmd) tea.Cmd
|
||
|
|
ExitInputMode()
|
||
|
|
SubmitInput() tea.Cmd
|
||
|
|
DeleteItem() tea.Cmd
|
||
|
|
ClampCursor()
|
||
|
|
AddItem() tea.Cmd
|
||
|
|
AddItemToPool(item core.Item)
|
||
|
|
UpdateItem() tea.Cmd
|
||
|
|
UpdateCurrentItem(newItem core.Item)
|
||
|
|
SelectItem()
|
||
|
|
MoveItem()
|
||
|
|
MoveRight()
|
||
|
|
MoveUp()
|
||
|
|
MoveDown()
|
||
|
|
MoveLeft()
|
||
|
|
Save() core.SaveState
|
||
|
|
Load(data core.SaveState)
|
||
|
|
ToggleHelp()
|
||
|
|
}
|
||
|
|
|
||
|
|
type state struct {
|
||
|
|
tiers []core.Tier
|
||
|
|
|
||
|
|
cursorRow int
|
||
|
|
cursorCol int
|
||
|
|
dragging bool
|
||
|
|
SourceRow int
|
||
|
|
SourceCol int
|
||
|
|
dragBuffer core.Item
|
||
|
|
|
||
|
|
windowWidth int
|
||
|
|
windowHeight int
|
||
|
|
|
||
|
|
mode Mode
|
||
|
|
input textinput.Model
|
||
|
|
InputCallback func(string, State) tea.Cmd
|
||
|
|
inputTitle string
|
||
|
|
|
||
|
|
statusMsg string
|
||
|
|
}
|
||
|
|
|
||
|
|
type option func(*state)
|
||
|
|
|
||
|
|
func New(opts ...option) State {
|
||
|
|
ti := textinput.New()
|
||
|
|
ti.CharLimit = 50
|
||
|
|
ti.Width = 30
|
||
|
|
|
||
|
|
state := &state{
|
||
|
|
tiers: []core.Tier{
|
||
|
|
{Name: "S Tier", Color: styles.TierColors[0], Items: []core.Item{}},
|
||
|
|
{Name: "A Tier", Color: styles.TierColors[1], Items: []core.Item{}},
|
||
|
|
{Name: "B Tier", Color: styles.TierColors[2], Items: []core.Item{}},
|
||
|
|
{Name: "C Tier", Color: styles.TierColors[3], Items: []core.Item{}},
|
||
|
|
{Name: "D Tier", Color: styles.TierColors[4], Items: []core.Item{}},
|
||
|
|
{Name: "Pool", Color: styles.TierColors[5], Items: []core.Item{}},
|
||
|
|
},
|
||
|
|
cursorRow: 5,
|
||
|
|
cursorCol: 0,
|
||
|
|
mode: NavigationMode,
|
||
|
|
input: ti,
|
||
|
|
statusMsg: "Ready.",
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, opt := range opts {
|
||
|
|
opt(state)
|
||
|
|
}
|
||
|
|
|
||
|
|
return state
|
||
|
|
}
|
||
|
|
|
||
|
|
func WithTiers(tiers []core.Tier) option {
|
||
|
|
return func(s *state) {
|
||
|
|
s.tiers = tiers
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func WithCursor(row, col int) option {
|
||
|
|
return func(s *state) {
|
||
|
|
s.cursorRow = row
|
||
|
|
s.cursorCol = col
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func WithMode(mode Mode) option {
|
||
|
|
return func(s *state) {
|
||
|
|
s.mode = mode
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func WithInput(input textinput.Model, title string) option {
|
||
|
|
return func(s *state) {
|
||
|
|
s.input = input
|
||
|
|
s.inputTitle = title
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func WithWindowWidth(width int) option {
|
||
|
|
return func(s *state) {
|
||
|
|
s.windowWidth = width
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) Mode() Mode {
|
||
|
|
return s.mode
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) InputTitle() string {
|
||
|
|
return s.inputTitle
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) Input() textinput.Model {
|
||
|
|
return s.input
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) UpdateInput(msg tea.Msg) tea.Cmd {
|
||
|
|
var cmd tea.Cmd
|
||
|
|
s.input, cmd = s.input.Update(msg)
|
||
|
|
return cmd
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) Tiers() []core.Tier {
|
||
|
|
return s.tiers
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) CursorRow() int {
|
||
|
|
return s.cursorRow
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) CursorCol() int {
|
||
|
|
return s.cursorCol
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) Dragging() bool {
|
||
|
|
return s.dragging
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) WindowHeight() int {
|
||
|
|
return s.windowHeight
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) WindowWidth() int {
|
||
|
|
return s.windowWidth
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) SetWindowSize(width int, height int) {
|
||
|
|
s.windowHeight = height
|
||
|
|
s.windowWidth = width
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) StatusMsg() string {
|
||
|
|
return s.statusMsg
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) SetStatus(msg string) {
|
||
|
|
s.statusMsg = msg
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) ToggleHelp() {
|
||
|
|
if s.mode == HelpMode {
|
||
|
|
s.mode = NavigationMode
|
||
|
|
} else {
|
||
|
|
s.mode = HelpMode
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) StartInputMode(title string, callback func(string, State) tea.Cmd) tea.Cmd {
|
||
|
|
s.mode = InputMode
|
||
|
|
s.inputTitle = title
|
||
|
|
s.InputCallback = callback
|
||
|
|
s.input.Reset()
|
||
|
|
s.input.Focus()
|
||
|
|
return textinput.Blink
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) ExitInputMode() {
|
||
|
|
s.mode = NavigationMode
|
||
|
|
s.input.Reset()
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) SubmitInput() tea.Cmd {
|
||
|
|
var cmd tea.Cmd
|
||
|
|
|
||
|
|
val := s.input.Value()
|
||
|
|
|
||
|
|
if s.InputCallback != nil {
|
||
|
|
cmd = s.InputCallback(val, s)
|
||
|
|
}
|
||
|
|
|
||
|
|
s.mode = NavigationMode
|
||
|
|
s.input.Reset()
|
||
|
|
return cmd
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) DeleteItem() tea.Cmd {
|
||
|
|
tier := &s.tiers[s.cursorRow]
|
||
|
|
|
||
|
|
if len(tier.Items) == 0 {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
tier.Items = append(tier.Items[:s.cursorCol], tier.Items[s.cursorCol+1:]...)
|
||
|
|
s.ClampCursor()
|
||
|
|
s.SetStatus("Item Deleted")
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) ClampCursor() {
|
||
|
|
rowLen := len(s.tiers[s.cursorRow].Items)
|
||
|
|
if s.cursorCol >= rowLen {
|
||
|
|
if rowLen == 0 {
|
||
|
|
s.cursorCol = 0
|
||
|
|
return
|
||
|
|
}
|
||
|
|
s.cursorCol = rowLen - 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) AddItem() tea.Cmd {
|
||
|
|
return s.StartInputMode("Add new item", func(val string, s State) tea.Cmd {
|
||
|
|
if val != "" {
|
||
|
|
s.AddItemToPool(core.Item(val))
|
||
|
|
s.SetStatus("Added: " + val)
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) AddItemToPool(item core.Item) {
|
||
|
|
poolIndex := len(s.tiers) - 1
|
||
|
|
s.tiers[poolIndex].Items = append(s.tiers[poolIndex].Items, item)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) UpdateItem() tea.Cmd {
|
||
|
|
if len(s.tiers[s.cursorRow].Items) == 0 {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
currentItem := string(s.tiers[s.cursorRow].Items[s.cursorCol])
|
||
|
|
|
||
|
|
cmd := s.StartInputMode("Rename Item", func(val string, m State) tea.Cmd {
|
||
|
|
if val != "" {
|
||
|
|
m.UpdateCurrentItem(core.Item(val))
|
||
|
|
m.SetStatus("Renamed to: " + val)
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
})
|
||
|
|
|
||
|
|
s.input.SetValue(currentItem)
|
||
|
|
|
||
|
|
s.input.CursorEnd()
|
||
|
|
|
||
|
|
return cmd
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) UpdateCurrentItem(newItem core.Item) {
|
||
|
|
s.tiers[s.cursorRow].Items[s.cursorCol] = newItem
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) SelectItem() {
|
||
|
|
if s.dragging {
|
||
|
|
s.dragging = false
|
||
|
|
} else {
|
||
|
|
if len(s.tiers[s.cursorRow].Items) > 0 {
|
||
|
|
s.dragging = true
|
||
|
|
s.SourceRow = s.cursorRow
|
||
|
|
s.SourceCol = s.cursorCol
|
||
|
|
s.dragBuffer = s.tiers[s.cursorRow].Items[s.cursorCol]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) MoveItem() {
|
||
|
|
sourceRow := s.SourceRow
|
||
|
|
sourceCol := s.SourceCol
|
||
|
|
item := s.dragBuffer
|
||
|
|
|
||
|
|
if sourceRow >= len(s.tiers) || sourceCol >= len(s.tiers[sourceRow].Items) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
oldRow := s.tiers[sourceRow].Items
|
||
|
|
s.tiers[sourceRow].Items = append(oldRow[:sourceCol], oldRow[sourceCol+1:]...)
|
||
|
|
|
||
|
|
targetRow := s.tiers[s.cursorRow].Items
|
||
|
|
|
||
|
|
if s.cursorCol > len(targetRow) {
|
||
|
|
s.cursorCol = len(targetRow)
|
||
|
|
}
|
||
|
|
|
||
|
|
s.SourceRow = s.cursorRow
|
||
|
|
s.SourceCol = s.cursorCol
|
||
|
|
|
||
|
|
newRow := make([]core.Item, len(targetRow)+1)
|
||
|
|
copy(newRow, targetRow[:s.cursorCol])
|
||
|
|
|
||
|
|
newRow[s.cursorCol] = item
|
||
|
|
copy(newRow[s.cursorCol+1:], targetRow[s.cursorCol:])
|
||
|
|
|
||
|
|
s.tiers[s.cursorRow].Items = newRow
|
||
|
|
s.SetStatus(fmt.Sprintf("Moved '%s' from %s to %s", item, s.tiers[sourceRow].Name, s.tiers[s.cursorRow].Name))
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) MoveRight() {
|
||
|
|
limit := len(s.tiers[s.cursorRow].Items) - 1
|
||
|
|
|
||
|
|
if s.dragging {
|
||
|
|
limit = len(s.tiers[s.cursorRow].Items)
|
||
|
|
}
|
||
|
|
|
||
|
|
if s.cursorCol < limit {
|
||
|
|
s.cursorCol++
|
||
|
|
} else if !s.dragging && len(s.tiers[s.cursorRow].Items) == 0 {
|
||
|
|
s.cursorCol = 0
|
||
|
|
}
|
||
|
|
|
||
|
|
if s.dragging {
|
||
|
|
s.MoveItem()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) MoveUp() {
|
||
|
|
if s.cursorRow > 0 {
|
||
|
|
s.cursorRow--
|
||
|
|
s.ClampCursor()
|
||
|
|
|
||
|
|
if s.dragging {
|
||
|
|
s.MoveItem()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) MoveDown() {
|
||
|
|
if s.cursorRow < len(s.tiers)-1 {
|
||
|
|
s.cursorRow++
|
||
|
|
s.ClampCursor()
|
||
|
|
|
||
|
|
if s.dragging {
|
||
|
|
s.MoveItem()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) MoveLeft() {
|
||
|
|
if s.cursorCol > 0 {
|
||
|
|
s.cursorCol--
|
||
|
|
|
||
|
|
if s.dragging {
|
||
|
|
s.MoveItem()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) Save() core.SaveState {
|
||
|
|
state := core.SaveState{}
|
||
|
|
|
||
|
|
for _, t := range s.tiers {
|
||
|
|
state.Tiers = append(state.Tiers, struct {
|
||
|
|
Name string `json:"name"`
|
||
|
|
Items []core.Item `json:"items"`
|
||
|
|
}{Name: t.Name, Items: t.Items})
|
||
|
|
}
|
||
|
|
|
||
|
|
return state
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *state) Load(data core.SaveState) {
|
||
|
|
if len(data.Tiers) == len(s.tiers) {
|
||
|
|
for i, savedTier := range data.Tiers {
|
||
|
|
s.tiers[i].Items = savedTier.Items
|
||
|
|
}
|
||
|
|
|
||
|
|
s.cursorRow = 0
|
||
|
|
s.cursorCol = 0
|
||
|
|
s.dragging = false
|
||
|
|
}
|
||
|
|
}
|