Add profile management

This commit is contained in:
Vilsol 2021-12-04 20:02:05 +02:00
parent b27e6fc9f1
commit 30c8dcf3cd
21 changed files with 552 additions and 78 deletions

View file

@ -22,7 +22,8 @@ func TestAddInstallation(t *testing.T) {
testza.AssertNoError(t, err) testza.AssertNoError(t, err)
profileName := "InstallationTest" profileName := "InstallationTest"
profile := ctx.Profiles.AddProfile(profileName) profile, err := ctx.Profiles.AddProfile(profileName)
testza.AssertNoError(t, err)
testza.AssertNoError(t, profile.AddMod("AreaActions", ">=1.6.5")) testza.AssertNoError(t, profile.AddMod("AreaActions", ">=1.6.5"))
testza.AssertNoError(t, profile.AddMod("ArmorModules__Modpack_All", ">=1.4.1")) testza.AssertNoError(t, profile.AddMod("ArmorModules__Modpack_All", ">=1.4.1"))

View file

@ -12,10 +12,10 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
) )
const defaultProfileName = "Default" const DefaultProfileName = "Default"
var defaultProfile = Profile{ var defaultProfile = Profile{
Name: defaultProfileName, Name: DefaultProfileName,
} }
type ProfilesVersion int type ProfilesVersion int
@ -29,7 +29,7 @@ const (
type Profiles struct { type Profiles struct {
Version ProfilesVersion `json:"version"` Version ProfilesVersion `json:"version"`
Profiles []*Profile `json:"profiles"` Profiles map[string]*Profile `json:"profiles"`
SelectedProfile string `json:"selected_profile"` SelectedProfile string `json:"selected_profile"`
} }
@ -67,7 +67,9 @@ func InitProfiles() (*Profiles, error) {
emptyProfiles := Profiles{ emptyProfiles := Profiles{
Version: nextProfilesVersion - 1, Version: nextProfilesVersion - 1,
Profiles: []*Profile{&defaultProfile}, Profiles: map[string]*Profile{
DefaultProfileName: &defaultProfile,
},
} }
if err := emptyProfiles.Save(); err != nil { if err := emptyProfiles.Save(); err != nil {
@ -90,12 +92,14 @@ func InitProfiles() (*Profiles, error) {
} }
if len(profiles.Profiles) == 0 { if len(profiles.Profiles) == 0 {
profiles.Profiles = []*Profile{&defaultProfile} profiles.Profiles = map[string]*Profile{
profiles.SelectedProfile = defaultProfileName DefaultProfileName: &defaultProfile,
}
profiles.SelectedProfile = DefaultProfileName
} }
if profiles.SelectedProfile == "" { if profiles.SelectedProfile == "" || profiles.Profiles[profiles.SelectedProfile] == nil {
profiles.SelectedProfile = profiles.Profiles[0].Name profiles.SelectedProfile = DefaultProfileName
} }
return &profiles, nil return &profiles, nil
@ -125,38 +129,53 @@ func (p *Profiles) Save() error {
} }
// AddProfile adds a new profile with the given name to the profiles list. // AddProfile adds a new profile with the given name to the profiles list.
func (p *Profiles) AddProfile(name string) *Profile { func (p *Profiles) AddProfile(name string) (*Profile, error) {
profile := &Profile{ if _, ok := p.Profiles[name]; ok {
return nil, fmt.Errorf("profile with name %s already exists", name)
}
p.Profiles[name] = &Profile{
Name: name, Name: name,
} }
p.Profiles = append(p.Profiles, profile) return p.Profiles[name], nil
return profile
} }
// DeleteProfile deletes the profile with the given name. // DeleteProfile deletes the profile with the given name.
func (p *Profiles) DeleteProfile(name string) { func (p *Profiles) DeleteProfile(name string) error {
i := 0 if _, ok := p.Profiles[name]; ok {
for _, profile := range p.Profiles { delete(p.Profiles, name)
if profile.Name == name {
break if p.SelectedProfile == name {
p.SelectedProfile = DefaultProfileName
} }
i++ return nil
} }
if i < len(p.Profiles) { return fmt.Errorf("profile with name %s does not exist", name)
p.Profiles = append(p.Profiles[:i], p.Profiles[i+1:]...)
}
} }
// GetProfile returns the profile with the given name or nil if it doesn't exist. // GetProfile returns the profile with the given name or nil if it doesn't exist.
func (p *Profiles) GetProfile(name string) *Profile { func (p *Profiles) GetProfile(name string) *Profile {
for _, profile := range p.Profiles { return p.Profiles[name]
if profile.Name == name {
return profile
} }
func (p *Profiles) RenameProfile(oldName string, newName string) error {
if _, ok := p.Profiles[newName]; ok {
return fmt.Errorf("profile with name %s already exists", newName)
}
if _, ok := p.Profiles[oldName]; !ok {
return fmt.Errorf("profile with name %s does not exist", oldName)
}
p.Profiles[oldName].Name = newName
p.Profiles[newName] = p.Profiles[oldName]
delete(p.Profiles, oldName)
if p.SelectedProfile == oldName {
p.SelectedProfile = newName
} }
return nil return nil

View file

@ -67,6 +67,9 @@ func Execute() {
// Execute tea as default // Execute tea as default
cmd, _, err := rootCmd.Find(os.Args[1:]) cmd, _, err := rootCmd.Find(os.Args[1:])
// Allow opening via explorer
cobra.MousetrapHelpText = ""
cli := len(os.Args) >= 2 && os.Args[1] == "cli" cli := len(os.Args) >= 2 && os.Args[1] == "cli"
if (len(os.Args) <= 1 || os.Args[1] != "help") && (err != nil || cmd == rootCmd) { if (len(os.Args) <= 1 || os.Args[1] != "help") && (err != nil || cmd == rootCmd) {
args := append([]string{"cli"}, os.Args[1:]...) args := append([]string{"cli"}, os.Args[1:]...)

View file

@ -12,8 +12,6 @@ import (
) )
type rootModel struct { type rootModel struct {
currentProfile *cli.Profile
currentInstallation *cli.Installation
global *cli.GlobalContext global *cli.GlobalContext
apiClient graphql.Client apiClient graphql.Client
currentSize tea.WindowSizeMsg currentSize tea.WindowSizeMsg
@ -23,8 +21,6 @@ type rootModel struct {
func newModel(global *cli.GlobalContext) *rootModel { func newModel(global *cli.GlobalContext) *rootModel {
m := &rootModel{ m := &rootModel{
global: global, global: global,
currentProfile: global.Profiles.GetProfile(global.Profiles.SelectedProfile),
currentInstallation: global.Installations.GetInstallation(global.Installations.SelectedInstallation),
apiClient: ficsit.InitAPI(), apiClient: ficsit.InitAPI(),
currentSize: tea.WindowSizeMsg{ currentSize: tea.WindowSizeMsg{
Width: 20, Width: 20,
@ -38,21 +34,19 @@ func newModel(global *cli.GlobalContext) *rootModel {
} }
func (m *rootModel) GetCurrentProfile() *cli.Profile { func (m *rootModel) GetCurrentProfile() *cli.Profile {
return m.currentProfile return m.global.Profiles.GetProfile(m.global.Profiles.SelectedProfile)
} }
func (m *rootModel) SetCurrentProfile(profile *cli.Profile) error { func (m *rootModel) SetCurrentProfile(profile *cli.Profile) error {
m.currentProfile = profile
m.global.Profiles.SelectedProfile = profile.Name m.global.Profiles.SelectedProfile = profile.Name
return m.global.Save() return m.global.Save()
} }
func (m *rootModel) GetCurrentInstallation() *cli.Installation { func (m *rootModel) GetCurrentInstallation() *cli.Installation {
return m.currentInstallation return m.global.Installations.GetInstallation(m.global.Installations.SelectedInstallation)
} }
func (m *rootModel) SetCurrentInstallation(installation *cli.Installation) error { func (m *rootModel) SetCurrentInstallation(installation *cli.Installation) error {
m.currentInstallation = installation
m.global.Installations.SelectedInstallation = installation.Path m.global.Installations.SelectedInstallation = installation.Path
return m.global.Save() return m.global.Save()
} }
@ -82,7 +76,7 @@ func (m *rootModel) GetGlobal() *cli.GlobalContext {
} }
func RunTea(global *cli.GlobalContext) error { func RunTea(global *cli.GlobalContext) error {
if err := tea.NewProgram(scenes.NewMainMenu(newModel(global))).Start(); err != nil { if err := tea.NewProgram(scenes.NewMainMenu(newModel(global)), tea.WithAltScreen(), tea.WithMouseCellMotion()).Start(); err != nil {
return errors.Wrap(err, "internal tea error") return errors.Wrap(err, "internal tea error")
} }
return nil return nil

5
tea/scenes/errors.go Normal file
View file

@ -0,0 +1,5 @@
package scenes
const (
ErrorFailedAddMod = "failed to add mod"
)

View file

@ -1,10 +1,11 @@
package scenes package scenes
import ( import (
"time"
"github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/davecgh/go-spew/spew"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/satisfactorymodding/ficsit-cli/tea/components" "github.com/satisfactorymodding/ficsit-cli/tea/components"
"github.com/satisfactorymodding/ficsit-cli/tea/utils" "github.com/satisfactorymodding/ficsit-cli/tea/utils"
@ -27,7 +28,11 @@ func NewExitMenu(root components.RootModel) tea.Model {
ItemTitle: "Exit Saving Changes", ItemTitle: "Exit Saving Changes",
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) { Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
if err := root.GetGlobal().Save(); err != nil { if err := root.GetGlobal().Save(); err != nil {
panic(err) // TODO message := "failed to save"
menu := currentModel.(exitMenu)
log.Error().Err(err).Msg(message)
cmd := menu.list.NewStatusMessage(message)
return currentModel, cmd
} }
return currentModel, tea.Quit return currentModel, tea.Quit
}, },
@ -47,6 +52,7 @@ func NewExitMenu(root components.RootModel) tea.Model {
model.list.Styles = utils.ListStyles model.list.Styles = utils.ListStyles
model.list.DisableQuitKeybindings() model.list.DisableQuitKeybindings()
model.list.SetSize(model.list.Width(), model.list.Height()) model.list.SetSize(model.list.Width(), model.list.Height())
model.list.StatusMessageLifetime = time.Second * 3
return model return model
} }
@ -56,7 +62,6 @@ func (m exitMenu) Init() tea.Cmd {
} }
func (m exitMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m exitMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
log.Warn().Msg(spew.Sdump(msg))
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyMsg: case tea.KeyMsg:
switch keypress := msg.String(); keypress { switch keypress := msg.String(); keypress {

View file

@ -3,4 +3,5 @@ package scenes
const ( const (
KeyControlC = "ctrl+c" KeyControlC = "ctrl+c"
KeyEnter = "enter" KeyEnter = "enter"
KeyEscape = "esc"
) )

View file

@ -1,10 +1,11 @@
package scenes package scenes
import ( import (
"time"
"github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/davecgh/go-spew/spew"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/satisfactorymodding/ficsit-cli/tea/components" "github.com/satisfactorymodding/ficsit-cli/tea/components"
"github.com/satisfactorymodding/ficsit-cli/tea/utils" "github.com/satisfactorymodding/ficsit-cli/tea/utils"
@ -55,7 +56,10 @@ func NewMainMenu(root components.RootModel) tea.Model {
ItemTitle: "Save", ItemTitle: "Save",
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) { Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
if err := root.GetGlobal().Save(); err != nil { if err := root.GetGlobal().Save(); err != nil {
panic(err) // TODO Handle Error menu := currentModel.(exitMenu)
log.Error().Err(err).Msg(ErrorFailedAddMod)
cmd := menu.list.NewStatusMessage(ErrorFailedAddMod)
return currentModel, cmd
} }
return nil, nil return nil, nil
}, },
@ -75,6 +79,8 @@ func NewMainMenu(root components.RootModel) tea.Model {
model.list.Title = "Main Menu" model.list.Title = "Main Menu"
model.list.Styles = utils.ListStyles model.list.Styles = utils.ListStyles
model.list.SetSize(model.list.Width(), model.list.Height()) model.list.SetSize(model.list.Width(), model.list.Height())
model.list.StatusMessageLifetime = time.Second * 3
model.list.DisableQuitKeybindings()
return model return model
} }
@ -84,7 +90,6 @@ func (m mainMenu) Init() tea.Cmd {
} }
func (m mainMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m mainMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
log.Warn().Msg(spew.Sdump(msg))
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyMsg: case tea.KeyMsg:
switch keypress := msg.String(); keypress { switch keypress := msg.String(); keypress {
@ -107,7 +112,7 @@ func (m mainMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil return m, nil
} }
} }
return m, tea.Quit return m, nil
default: default:
var cmd tea.Cmd var cmd tea.Cmd
m.list, cmd = m.list.Update(msg) m.list, cmd = m.list.Update(msg)

17
tea/scenes/messages.go Normal file
View file

@ -0,0 +1,17 @@
package scenes
import (
tea "github.com/charmbracelet/bubbletea"
)
type updateProfileList struct{}
func updateProfileListCmd() tea.Msg {
return updateProfileList{}
}
type updateProfileNames struct{}
func updateProfileNamesCmd() tea.Msg {
return updateProfileNames{}
}

View file

@ -1,10 +1,11 @@
package scenes package scenes
import ( import (
"time"
"github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/davecgh/go-spew/spew"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/satisfactorymodding/ficsit-cli/tea/components" "github.com/satisfactorymodding/ficsit-cli/tea/components"
"github.com/satisfactorymodding/ficsit-cli/tea/utils" "github.com/satisfactorymodding/ficsit-cli/tea/utils"
@ -49,7 +50,10 @@ func NewModMenu(root components.RootModel, parent tea.Model, mod utils.Mod) tea.
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) { Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
err := root.GetCurrentProfile().AddMod(mod.Reference, ">=0.0.0") err := root.GetCurrentProfile().AddMod(mod.Reference, ">=0.0.0")
if err != nil { if err != nil {
panic(err) // TODO Handle Error menu := currentModel.(exitMenu)
log.Error().Err(err).Msg(ErrorFailedAddMod)
cmd := menu.list.NewStatusMessage(ErrorFailedAddMod)
return currentModel, cmd
} }
return currentModel.(modMenu).parent, nil return currentModel.(modMenu).parent, nil
}, },
@ -79,6 +83,8 @@ func NewModMenu(root components.RootModel, parent tea.Model, mod utils.Mod) tea.
model.list.Styles = utils.ListStyles model.list.Styles = utils.ListStyles
model.list.SetSize(model.list.Width(), model.list.Height()) model.list.SetSize(model.list.Width(), model.list.Height())
model.list.KeyMap.Quit.SetHelp("q", "back") model.list.KeyMap.Quit.SetHelp("q", "back")
model.list.StatusMessageLifetime = time.Second * 3
model.list.DisableQuitKeybindings()
return model return model
} }
@ -88,7 +94,6 @@ func (m modMenu) Init() tea.Cmd {
} }
func (m modMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m modMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
log.Warn().Msg(spew.Sdump(msg))
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyMsg: case tea.KeyMsg:
switch keypress := msg.String(); keypress { switch keypress := msg.String(); keypress {
@ -115,7 +120,7 @@ func (m modMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil return m, nil
} }
} }
return m, tea.Quit return m, nil
default: default:
var cmd tea.Cmd var cmd tea.Cmd
m.list, cmd = m.list.Update(msg) m.list, cmd = m.list.Update(msg)

View file

@ -5,6 +5,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/rs/zerolog/log"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
md "github.com/JohannesKaufmann/html-to-markdown" md "github.com/JohannesKaufmann/html-to-markdown"
@ -185,12 +187,14 @@ func (m modInfo) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
markdownDescription, err := converter.ConvertString(mod.Full_description) markdownDescription, err := converter.ConvertString(mod.Full_description)
if err != nil { if err != nil {
panic(err) // TODO Handle Error log.Error().Err(err).Msg("failed to convert html to markdown")
markdownDescription = mod.Full_description
} }
description, err := glamour.Render(markdownDescription, "dark") description, err := glamour.Render(markdownDescription, "dark")
if err != nil { if err != nil {
panic(err) // TODO Handle Error log.Error().Err(err).Msg("failed to render markdown")
description = mod.Full_description
} }
bottomPart := lipgloss.JoinHorizontal(lipgloss.Top, sidebar, strings.TrimSpace(description)) bottomPart := lipgloss.JoinHorizontal(lipgloss.Top, sidebar, strings.TrimSpace(description))

View file

@ -4,8 +4,6 @@ import (
"github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/davecgh/go-spew/spew"
"github.com/rs/zerolog/log"
"github.com/satisfactorymodding/ficsit-cli/tea/components" "github.com/satisfactorymodding/ficsit-cli/tea/components"
"github.com/satisfactorymodding/ficsit-cli/tea/utils" "github.com/satisfactorymodding/ficsit-cli/tea/utils"
) )
@ -41,15 +39,13 @@ func (m modSemver) Init() tea.Cmd {
} }
func (m modSemver) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m modSemver) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
log.Warn().Msg(spew.Sdump(msg))
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyMsg: case tea.KeyMsg:
switch keypress := msg.String(); keypress { switch keypress := msg.String(); keypress {
case KeyControlC: case KeyControlC:
return m, tea.Quit return m, tea.Quit
case "q": case KeyEscape:
newModel := NewExitMenu(m.root) return m.parent, nil
return newModel, newModel.Init()
case KeyEnter: case KeyEnter:
err := m.root.GetCurrentProfile().AddMod(m.mod.Reference, m.input.Value()) err := m.root.GetCurrentProfile().AddMod(m.mod.Reference, m.input.Value())
if err != nil { if err != nil {

View file

@ -1,10 +1,11 @@
package scenes package scenes
import ( import (
"time"
"github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/davecgh/go-spew/spew"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/satisfactorymodding/ficsit-cli/tea/components" "github.com/satisfactorymodding/ficsit-cli/tea/components"
"github.com/satisfactorymodding/ficsit-cli/tea/utils" "github.com/satisfactorymodding/ficsit-cli/tea/utils"
@ -48,7 +49,10 @@ func NewModVersion(root components.RootModel, parent tea.Model, mod utils.Mod) t
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) { Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
err := root.GetCurrentProfile().AddMod(mod.Reference, ">=0.0.0") err := root.GetCurrentProfile().AddMod(mod.Reference, ">=0.0.0")
if err != nil { if err != nil {
panic(err) // TODO Handle Error menu := currentModel.(exitMenu)
log.Error().Err(err).Msg(ErrorFailedAddMod)
cmd := menu.list.NewStatusMessage(ErrorFailedAddMod)
return currentModel, cmd
} }
return currentModel.(modMenu).parent, nil return currentModel.(modMenu).parent, nil
}, },
@ -63,6 +67,8 @@ func NewModVersion(root components.RootModel, parent tea.Model, mod utils.Mod) t
model.list.Styles = utils.ListStyles model.list.Styles = utils.ListStyles
model.list.SetSize(model.list.Width(), model.list.Height()) model.list.SetSize(model.list.Width(), model.list.Height())
model.list.KeyMap.Quit.SetHelp("q", "back") model.list.KeyMap.Quit.SetHelp("q", "back")
model.list.StatusMessageLifetime = time.Second * 3
model.list.DisableQuitKeybindings()
return model return model
} }
@ -72,7 +78,6 @@ func (m modVersionMenu) Init() tea.Cmd {
} }
func (m modVersionMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m modVersionMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
log.Warn().Msg(spew.Sdump(msg))
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyMsg: case tea.KeyMsg:
switch keypress := msg.String(); keypress { switch keypress := msg.String(); keypress {
@ -99,7 +104,7 @@ func (m modVersionMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil return m, nil
} }
} }
return m, tea.Quit return m, nil
default: default:
var cmd tea.Cmd var cmd tea.Cmd
m.list, cmd = m.list.Update(msg) m.list, cmd = m.list.Update(msg)

View file

@ -10,8 +10,6 @@ import (
"github.com/charmbracelet/bubbles/spinner" "github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/davecgh/go-spew/spew"
"github.com/rs/zerolog/log"
"github.com/satisfactorymodding/ficsit-cli/ficsit" "github.com/satisfactorymodding/ficsit-cli/ficsit"
"github.com/satisfactorymodding/ficsit-cli/tea/components" "github.com/satisfactorymodding/ficsit-cli/tea/components"
"github.com/satisfactorymodding/ficsit-cli/tea/utils" "github.com/satisfactorymodding/ficsit-cli/tea/utils"
@ -55,6 +53,8 @@ func NewMods(root components.RootModel, parent tea.Model) tea.Model {
l.Styles = utils.ListStyles l.Styles = utils.ListStyles
l.SetSize(l.Width(), l.Height()) l.SetSize(l.Width(), l.Height())
l.KeyMap.Quit.SetHelp("q", "back") l.KeyMap.Quit.SetHelp("q", "back")
l.DisableQuitKeybindings()
l.AdditionalShortHelpKeys = func() []key.Binding { l.AdditionalShortHelpKeys = func() []key.Binding {
return []key.Binding{ return []key.Binding{
key.NewBinding(key.WithHelp("s", "sort")), key.NewBinding(key.WithHelp("s", "sort")),
@ -62,6 +62,13 @@ func NewMods(root components.RootModel, parent tea.Model) tea.Model {
} }
} }
l.AdditionalFullHelpKeys = func() []key.Binding {
return []key.Binding{
key.NewBinding(key.WithHelp("s", "sort")),
key.NewBinding(key.WithHelp("o", "order")),
}
}
sortFieldList := list.NewModel([]list.Item{ sortFieldList := list.NewModel([]list.Item{
utils.SimpleItem{ utils.SimpleItem{
ItemTitle: "Name", ItemTitle: "Name",
@ -101,6 +108,7 @@ func NewMods(root components.RootModel, parent tea.Model) tea.Model {
sortFieldList.Styles = utils.ListStyles sortFieldList.Styles = utils.ListStyles
sortFieldList.SetSize(l.Width(), l.Height()) sortFieldList.SetSize(l.Width(), l.Height())
sortFieldList.KeyMap.Quit.SetHelp("q", "back") sortFieldList.KeyMap.Quit.SetHelp("q", "back")
sortFieldList.DisableQuitKeybindings()
sortOrderList := list.NewModel([]list.Item{ sortOrderList := list.NewModel([]list.Item{
utils.SimpleItem{ utils.SimpleItem{
@ -131,6 +139,7 @@ func NewMods(root components.RootModel, parent tea.Model) tea.Model {
sortOrderList.Styles = utils.ListStyles sortOrderList.Styles = utils.ListStyles
sortOrderList.SetSize(l.Width(), l.Height()) sortOrderList.SetSize(l.Width(), l.Height())
sortOrderList.KeyMap.Quit.SetHelp("q", "back") sortOrderList.KeyMap.Quit.SetHelp("q", "back")
sortOrderList.DisableQuitKeybindings()
m := &modsList{ m := &modsList{
root: root, root: root,
@ -196,7 +205,9 @@ func (m modsList) Init() tea.Cmd {
} }
func (m modsList) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m modsList) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
log.Info().Msg(spew.Sdump(msg)) // List enables its own keybindings when they were previously disabled
m.list.DisableQuitKeybindings()
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyMsg: case tea.KeyMsg:
if m.list.SettingFilter() { if m.list.SettingFilter() {

67
tea/scenes/new_profile.go Normal file
View file

@ -0,0 +1,67 @@
package scenes
import (
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/satisfactorymodding/ficsit-cli/tea/components"
"github.com/satisfactorymodding/ficsit-cli/tea/utils"
)
var _ tea.Model = (*newProfile)(nil)
type newProfile struct {
root components.RootModel
parent tea.Model
input textinput.Model
title string
}
func NewNewProfile(root components.RootModel, parent tea.Model) tea.Model {
model := newProfile{
root: root,
parent: parent,
input: textinput.NewModel(),
title: utils.NonListTitleStyle.Render("New Profile"),
}
model.input.Focus()
model.input.Width = root.Size().Width
return model
}
func (m newProfile) Init() tea.Cmd {
return nil
}
func (m newProfile) 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:
if _, err := m.root.GetGlobal().Profiles.AddProfile(m.input.Value()); err != nil {
panic(err) // TODO Handle Error
}
return m.parent, updateProfileListCmd
default:
var cmd tea.Cmd
m.input, cmd = m.input.Update(msg)
return m, cmd
}
case tea.WindowSizeMsg:
m.root.SetSize(msg)
}
return m, nil
}
func (m newProfile) View() string {
inputView := lipgloss.NewStyle().Padding(1, 2).Render(m.input.View())
return lipgloss.JoinVertical(lipgloss.Left, m.root.View(), m.title, inputView)
}

134
tea/scenes/profile.go Normal file
View file

@ -0,0 +1,134 @@
package scenes
import (
"fmt"
"time"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/satisfactorymodding/ficsit-cli/cli"
"github.com/satisfactorymodding/ficsit-cli/tea/components"
"github.com/satisfactorymodding/ficsit-cli/tea/utils"
)
var _ tea.Model = (*profile)(nil)
type profile struct {
root components.RootModel
list list.Model
parent tea.Model
profile *cli.Profile
hadRenamed bool
}
func NewProfile(root components.RootModel, parent tea.Model, profileData *cli.Profile) tea.Model {
model := profile{
root: root,
parent: parent,
profile: profileData,
}
items := []list.Item{
utils.SimpleItem{
ItemTitle: "Select",
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
if err := root.SetCurrentProfile(profileData); err != nil {
panic(err) // TODO Handle Error
}
return currentModel.(profile).parent, nil
},
},
}
if profileData.Name != cli.DefaultProfileName {
items = append(items,
utils.SimpleItem{
ItemTitle: "Rename",
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
newModel := NewRenameProfile(root, currentModel, profileData)
return newModel, newModel.Init()
},
},
utils.SimpleItem{
ItemTitle: "Delete",
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
if err := root.GetGlobal().Profiles.DeleteProfile(profileData.Name); err != nil {
panic(err) // TODO Handle Error
}
return currentModel.(profile).parent, updateProfileListCmd
},
},
)
}
model.list = list.NewModel(items, utils.NewItemDelegate(), root.Size().Width, root.Size().Height-root.Height())
model.list.SetShowStatusBar(false)
model.list.SetFilteringEnabled(false)
model.list.Title = fmt.Sprintf("Profile: %s", profileData.Name)
model.list.Styles = utils.ListStyles
model.list.SetSize(model.list.Width(), model.list.Height())
model.list.StatusMessageLifetime = time.Second * 3
model.list.DisableQuitKeybindings()
return model
}
func (m profile) Init() tea.Cmd {
return nil
}
func (m profile) 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 "q":
if m.parent != nil {
m.parent.Update(m.root.Size())
if m.hadRenamed {
return m.parent, updateProfileNamesCmd
}
return m.parent, nil
}
return m, nil
case KeyEnter:
i, ok := m.list.SelectedItem().(utils.SimpleItem)
if ok {
if i.Activate != nil {
newModel, cmd := i.Activate(msg, m)
if newModel != nil || cmd != nil {
if newModel == nil {
newModel = m
}
return newModel, cmd
}
return m, nil
}
}
return m, nil
default:
var cmd tea.Cmd
m.list, cmd = m.list.Update(msg)
return m, cmd
}
case tea.WindowSizeMsg:
top, right, bottom, left := lipgloss.NewStyle().Margin(2, 2).GetMargin()
m.list.SetSize(msg.Width-left-right, msg.Height-top-bottom)
m.root.SetSize(msg)
case updateProfileNames:
m.hadRenamed = true
m.list.Title = fmt.Sprintf("Profile: %s", m.profile.Name)
}
return m, nil
}
func (m profile) View() string {
return lipgloss.JoinVertical(lipgloss.Left, m.root.View(), m.list.View())
}

View file

@ -1,10 +1,140 @@
package scenes package scenes
import ( import (
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/satisfactorymodding/ficsit-cli/tea/components" "github.com/satisfactorymodding/ficsit-cli/tea/components"
"github.com/satisfactorymodding/ficsit-cli/tea/utils"
) )
var _ tea.Model = (*profiles)(nil)
type profiles struct {
root components.RootModel
list list.Model
parent tea.Model
}
func NewProfiles(root components.RootModel, parent tea.Model) tea.Model { func NewProfiles(root components.RootModel, parent tea.Model) tea.Model {
l := list.NewModel(profilesToList(root), utils.NewItemDelegate(), root.Size().Width, root.Size().Height-root.Height())
l.SetShowStatusBar(true)
l.SetFilteringEnabled(true)
l.SetSpinner(spinner.MiniDot)
l.Title = "Profiles"
l.Styles = utils.ListStyles
l.SetSize(l.Width(), l.Height())
l.KeyMap.Quit.SetHelp("q", "back")
l.DisableQuitKeybindings()
l.AdditionalShortHelpKeys = func() []key.Binding {
return []key.Binding{
key.NewBinding(key.WithHelp("n", "new profile")),
}
}
l.AdditionalFullHelpKeys = func() []key.Binding {
return []key.Binding{
key.NewBinding(key.WithHelp("n", "new profile")),
}
}
return &profiles{
root: root,
list: l,
parent: parent,
}
}
func (m profiles) Init() tea.Cmd {
return nil return nil
} }
func (m profiles) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// List enables its own keybindings when they were previously disabled
m.list.DisableQuitKeybindings()
switch msg := msg.(type) {
case tea.KeyMsg:
if m.list.SettingFilter() {
var cmd tea.Cmd
m.list, cmd = m.list.Update(msg)
return m, cmd
}
switch keypress := msg.String(); keypress {
case "n":
newModel := NewNewProfile(m.root, m)
return newModel, newModel.Init()
case KeyControlC:
return m, tea.Quit
case "q":
if m.parent != nil {
m.parent.Update(m.root.Size())
return m.parent, nil
}
return m, tea.Quit
case KeyEnter:
i, ok := m.list.SelectedItem().(utils.SimpleItem)
if ok {
if i.Activate != nil {
newModel, cmd := i.Activate(msg, m)
if newModel != nil || cmd != nil {
if newModel == nil {
newModel = m
}
return newModel, cmd
}
return m, nil
}
}
return m, nil
}
case tea.WindowSizeMsg:
top, right, bottom, left := lipgloss.NewStyle().Margin(m.root.Height(), 2, 0).GetMargin()
m.list.SetSize(msg.Width-left-right, msg.Height-top-bottom)
m.root.SetSize(msg)
case updateProfileList:
m.list.ResetSelected()
cmd := m.list.SetItems(profilesToList(m.root))
// Done to refresh keymap
m.list.SetFilteringEnabled(m.list.FilteringEnabled())
return m, cmd
case updateProfileNames:
cmd := m.list.SetItems(profilesToList(m.root))
// Done to refresh keymap
m.list.SetFilteringEnabled(m.list.FilteringEnabled())
return m, cmd
}
var cmd tea.Cmd
m.list, cmd = m.list.Update(msg)
return m, cmd
}
func (m profiles) View() string {
return lipgloss.JoinVertical(lipgloss.Left, m.root.View(), m.list.View())
}
func profilesToList(root components.RootModel) []list.Item {
items := make([]list.Item, len(root.GetGlobal().Profiles.Profiles))
i := 0
for _, profile := range root.GetGlobal().Profiles.Profiles {
temp := profile
items[i] = utils.SimpleItem{
ItemTitle: temp.Name,
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
newModel := NewProfile(root, currentModel, temp)
return newModel, newModel.Init()
},
}
i++
}
return items
}

View file

@ -0,0 +1,73 @@
package scenes
import (
"fmt"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/satisfactorymodding/ficsit-cli/cli"
"github.com/satisfactorymodding/ficsit-cli/tea/components"
"github.com/satisfactorymodding/ficsit-cli/tea/utils"
)
var _ tea.Model = (*renameProfile)(nil)
type renameProfile struct {
root components.RootModel
parent tea.Model
input textinput.Model
title string
oldName string
}
func NewRenameProfile(root components.RootModel, parent tea.Model, profileData *cli.Profile) tea.Model {
model := renameProfile{
root: root,
parent: parent,
input: textinput.NewModel(),
title: utils.NonListTitleStyle.Render(fmt.Sprintf("Rename Profile: %s", profileData.Name)),
oldName: profileData.Name,
}
model.input.SetValue(profileData.Name)
model.input.Focus()
model.input.Width = root.Size().Width
return model
}
func (m renameProfile) Init() tea.Cmd {
return nil
}
func (m renameProfile) 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:
if err := m.root.GetGlobal().Profiles.RenameProfile(m.oldName, m.input.Value()); err != nil {
panic(err) // TODO Handle Error
}
return m.parent, updateProfileNamesCmd
default:
var cmd tea.Cmd
m.input, cmd = m.input.Update(msg)
return m, cmd
}
case tea.WindowSizeMsg:
m.root.SetSize(msg)
}
return m, nil
}
func (m renameProfile) View() string {
inputView := lipgloss.NewStyle().Padding(1, 2).Render(m.input.View())
return lipgloss.JoinVertical(lipgloss.Left, m.root.View(), m.title, inputView)
}

View file

@ -8,8 +8,6 @@ import (
"github.com/charmbracelet/bubbles/spinner" "github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/davecgh/go-spew/spew"
"github.com/rs/zerolog/log"
"github.com/satisfactorymodding/ficsit-cli/ficsit" "github.com/satisfactorymodding/ficsit-cli/ficsit"
"github.com/satisfactorymodding/ficsit-cli/tea/components" "github.com/satisfactorymodding/ficsit-cli/tea/components"
"github.com/satisfactorymodding/ficsit-cli/tea/utils" "github.com/satisfactorymodding/ficsit-cli/tea/utils"
@ -33,6 +31,7 @@ func NewModVersionList(root components.RootModel, parent tea.Model, mod utils.Mo
l.Styles = utils.ListStyles l.Styles = utils.ListStyles
l.SetSize(l.Width(), l.Height()) l.SetSize(l.Width(), l.Height())
l.KeyMap.Quit.SetHelp("q", "back") l.KeyMap.Quit.SetHelp("q", "back")
l.DisableQuitKeybindings()
m := &selectModVersionList{ m := &selectModVersionList{
root: root, root: root,
@ -93,7 +92,6 @@ func (m selectModVersionList) Init() tea.Cmd {
} }
func (m selectModVersionList) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m selectModVersionList) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
log.Info().Msg(spew.Sdump(msg))
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyMsg: case tea.KeyMsg:
switch keypress := msg.String(); keypress { switch keypress := msg.String(); keypress {
@ -119,7 +117,7 @@ func (m selectModVersionList) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil return m, nil
} }
} }
return m, tea.Quit return m, nil
default: default:
var cmd tea.Cmd var cmd tea.Cmd
m.list, cmd = m.list.Update(msg) m.list, cmd = m.list.Update(msg)

View file

@ -9,6 +9,7 @@ var (
ListStyles list.Styles ListStyles list.Styles
LabelStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("202")) LabelStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("202"))
TitleStyle = list.DefaultStyles().Title.Background(lipgloss.Color("22")) TitleStyle = list.DefaultStyles().Title.Background(lipgloss.Color("22"))
NonListTitleStyle = TitleStyle.Copy().MarginLeft(2).Background(lipgloss.Color("22"))
) )
func init() { func init() {