ficsit-cli-flake/tea/scenes/new_installation.go

269 lines
6.8 KiB
Go
Raw Normal View History

2022-04-14 01:27:39 +00:00
package scenes
import (
"fmt"
"io"
"os"
2022-06-05 01:56:46 +00:00
"path/filepath"
"sort"
"time"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
"github.com/charmbracelet/bubbles/spinner"
2022-04-14 01:27:39 +00:00
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/muesli/reflow/truncate"
"github.com/sahilm/fuzzy"
2022-04-14 01:27:39 +00:00
"github.com/satisfactorymodding/ficsit-cli/tea/components"
"github.com/satisfactorymodding/ficsit-cli/tea/utils"
)
var _ tea.Model = (*newInstallation)(nil)
type newInstallation struct {
root components.RootModel
parent tea.Model
input textinput.Model
title string
error *components.ErrorComponent
dirList list.Model
2022-04-14 01:27:39 +00:00
}
func NewNewInstallation(root components.RootModel, parent tea.Model) tea.Model {
2022-06-06 23:55:26 +00:00
listDelegate := NewInstallListDelegate{ItemDelegate: utils.NewItemDelegate()}
l := list.New([]list.Item{}, listDelegate, root.Size().Width, root.Size().Height-root.Height())
l.SetShowStatusBar(true)
l.SetFilteringEnabled(false)
l.SetSpinner(spinner.MiniDot)
l.SetShowTitle(false)
l.Styles = utils.ListStyles
l.SetSize(l.Width(), l.Height())
l.KeyMap.Quit.SetHelp("esc", "back")
l.DisableQuitKeybindings()
l.SetShowHelp(false)
l.SetShowStatusBar(false)
2022-04-14 01:27:39 +00:00
model := newInstallation{
root: root,
parent: parent,
input: textinput.New(),
title: utils.NonListTitleStyle.Render("New Installation"),
dirList: l,
2022-04-14 01:27:39 +00:00
}
model.input.Focus()
model.input.Width = root.Size().Width
// TODO SSH/FTP/SFTP support
2022-04-14 01:27:39 +00:00
return model
}
func (m newInstallation) Init() tea.Cmd {
return textinput.Blink
2022-04-14 01:27:39 +00:00
}
func (m newInstallation) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch keypress := msg.String(); keypress {
case KeyControlC:
return m, tea.Quit
case KeyEscape:
return m.parent, nil
case KeyEnter:
newInstall, err := m.root.GetGlobal().Installations.AddInstallation(m.root.GetGlobal(), m.input.Value(), m.root.GetGlobal().Profiles.SelectedProfile)
if err != nil {
errorComponent, cmd := components.NewErrorComponent(err.Error(), time.Second*5)
m.error = errorComponent
return m, cmd
2022-04-14 01:27:39 +00:00
}
if m.root.GetCurrentInstallation() == nil {
if err := m.root.SetCurrentInstallation(newInstall); err != nil {
errorComponent, cmd := components.NewErrorComponent(err.Error(), time.Second*5)
m.error = errorComponent
return m, cmd
}
}
2022-04-14 01:27:39 +00:00
return m.parent, updateInstallationListCmd
case KeyTab:
var cmd tea.Cmd
m.input, cmd = m.input.Update(msg)
newDirItem := m.dirList.SelectedItem()
if newDirItem == nil {
break
}
newDir := newDirItem.(utils.SimpleItemExtra[newInstallation, string]).ItemTitle
newPath := ""
_, err := os.ReadDir(m.input.Value())
if err == nil {
2022-06-05 01:56:46 +00:00
newPath = filepath.Join(m.input.Value(), newDir)
} else {
2022-06-05 01:56:46 +00:00
newPath = filepath.Join(filepath.Dir(m.input.Value()), newDir)
}
m.input.SetValue(newPath + string(os.PathSeparator))
m.input.CursorEnd()
listCmd := m.dirList.SetItems(getDirItems(newPath))
m.dirList.ResetSelected()
return m, tea.Batch(cmd, listCmd)
2022-04-14 01:27:39 +00:00
default:
var cmd tea.Cmd
m.input, cmd = m.input.Update(msg)
cmd = tea.Batch(cmd, m.dirList.SetItems(getDirItems(m.input.Value())))
if m.dirList.Index() > len(m.dirList.Items())-1 {
m.dirList.ResetSelected()
}
if key.Matches(msg, m.dirList.KeyMap.CursorUp) || key.Matches(msg, m.dirList.KeyMap.CursorDown) {
var dirCmd tea.Cmd
m.dirList, dirCmd = m.dirList.Update(msg)
cmd = tea.Batch(cmd, dirCmd)
}
2022-04-14 01:27:39 +00:00
return m, cmd
}
case tea.WindowSizeMsg:
m.root.SetSize(msg)
case components.ErrorComponentTimeoutMsg:
m.error = nil
default:
var cmd tea.Cmd
m.input, cmd = m.input.Update(msg)
return m, cmd
2022-04-14 01:27:39 +00:00
}
return m, nil
}
func (m newInstallation) View() string {
inputView := lipgloss.NewStyle().Padding(1, 2).Render(m.input.View())
2022-06-05 00:38:02 +00:00
mandatory := lipgloss.JoinVertical(lipgloss.Left, m.root.View(), m.title, inputView)
if m.error != nil {
2022-06-05 00:38:02 +00:00
return lipgloss.JoinVertical(lipgloss.Left, mandatory, (*m.error).View())
}
if len(m.dirList.Items()) > 0 {
2022-06-05 01:56:46 +00:00
m.dirList.SetSize(m.dirList.Width(), m.root.Size().Height-lipgloss.Height(mandatory)-1)
return lipgloss.JoinVertical(lipgloss.Left, mandatory, m.dirList.View())
}
infoBox := lipgloss.NewStyle().
BorderStyle(lipgloss.ThickBorder()).
BorderForeground(lipgloss.Color("39")).
Padding(0, 1).
Margin(0, 0, 0, 2).
Render("Enter the path to the satisfactory installation")
return lipgloss.JoinVertical(lipgloss.Left, mandatory, infoBox)
}
// I know this is awful, but beats re-implementing the entire list model
var globalMatches []fuzzy.Match
func getDirItems(inputValue string) []list.Item {
filter := ""
dir, err := os.ReadDir(inputValue)
if err != nil {
2022-06-05 01:56:46 +00:00
dir, err = os.ReadDir(filepath.Dir(inputValue))
if err == nil {
2022-06-05 01:56:46 +00:00
filter = filepath.Base(inputValue)
}
}
newItems := make([]list.Item, 0)
globalMatches = nil
if inputValue != "" {
if filter != "" {
dirNames := make([]string, 0)
for _, entry := range dir {
if entry.IsDir() {
dirNames = append(dirNames, entry.Name())
}
}
matches := fuzzy.Find(filter, dirNames)
sort.Stable(matches)
for _, match := range matches {
newItems = append(newItems, utils.SimpleItemExtra[newInstallation, string]{
SimpleItem: utils.SimpleItem[newInstallation]{
ItemTitle: match.Str,
},
Extra: match.Str,
})
}
globalMatches = matches
} else {
for _, entry := range dir {
if entry.IsDir() {
newItems = append(newItems, utils.SimpleItemExtra[newInstallation, string]{
SimpleItem: utils.SimpleItem[newInstallation]{
ItemTitle: entry.Name(),
},
Extra: entry.Name(),
})
}
}
}
}
return newItems
2022-04-14 01:27:39 +00:00
}
2022-06-06 23:55:26 +00:00
type NewInstallListDelegate struct {
list.ItemDelegate
}
2022-06-06 23:55:26 +00:00
func (c NewInstallListDelegate) Render(w io.Writer, m list.Model, index int, item list.Item) {
realItem := item.(utils.SimpleItemExtra[newInstallation, string])
realDelegate := c.ItemDelegate.(list.DefaultDelegate)
title := realItem.Title()
s := &realDelegate.Styles
if m.Width() <= 0 {
return
}
textwidth := uint(m.Width() - s.NormalTitle.GetPaddingLeft() - s.NormalTitle.GetPaddingRight())
title = truncate.StringWithTail(title, textwidth, "…")
if index == m.Index() {
if globalMatches != nil {
unmatched := s.SelectedTitle.Inline(true)
matched := unmatched.Copy().Inherit(s.FilterMatch)
title = lipgloss.StyleRunes(title, globalMatches[index].MatchedIndexes, matched, unmatched)
}
title = s.SelectedTitle.Render(title)
} else {
if globalMatches != nil {
unmatched := s.NormalTitle.Inline(true)
matched := unmatched.Copy().Inherit(s.FilterMatch)
title = lipgloss.StyleRunes(title, globalMatches[index].MatchedIndexes, matched, unmatched)
}
title = s.NormalTitle.Render(title)
}
fmt.Fprintf(w, "%s", title)
}