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)
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("ArmorModules__Modpack_All", ">=1.4.1"))

View file

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

View file

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

View file

@ -12,20 +12,16 @@ import (
)
type rootModel struct {
currentProfile *cli.Profile
currentInstallation *cli.Installation
global *cli.GlobalContext
apiClient graphql.Client
currentSize tea.WindowSizeMsg
headerComponent tea.Model
global *cli.GlobalContext
apiClient graphql.Client
currentSize tea.WindowSizeMsg
headerComponent tea.Model
}
func newModel(global *cli.GlobalContext) *rootModel {
m := &rootModel{
global: global,
currentProfile: global.Profiles.GetProfile(global.Profiles.SelectedProfile),
currentInstallation: global.Installations.GetInstallation(global.Installations.SelectedInstallation),
apiClient: ficsit.InitAPI(),
global: global,
apiClient: ficsit.InitAPI(),
currentSize: tea.WindowSizeMsg{
Width: 20,
Height: 14,
@ -38,21 +34,19 @@ func newModel(global *cli.GlobalContext) *rootModel {
}
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 {
m.currentProfile = profile
m.global.Profiles.SelectedProfile = profile.Name
return m.global.Save()
}
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 {
m.currentInstallation = installation
m.global.Installations.SelectedInstallation = installation.Path
return m.global.Save()
}
@ -82,7 +76,7 @@ func (m *rootModel) GetGlobal() *cli.GlobalContext {
}
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 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
import (
"time"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"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/utils"
@ -27,7 +28,11 @@ func NewExitMenu(root components.RootModel) tea.Model {
ItemTitle: "Exit Saving Changes",
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
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
},
@ -47,6 +52,7 @@ func NewExitMenu(root components.RootModel) tea.Model {
model.list.Styles = utils.ListStyles
model.list.DisableQuitKeybindings()
model.list.SetSize(model.list.Width(), model.list.Height())
model.list.StatusMessageLifetime = time.Second * 3
return model
}
@ -56,7 +62,6 @@ func (m exitMenu) Init() tea.Cmd {
}
func (m exitMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
log.Warn().Msg(spew.Sdump(msg))
switch msg := msg.(type) {
case tea.KeyMsg:
switch keypress := msg.String(); keypress {

View file

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

View file

@ -1,10 +1,11 @@
package scenes
import (
"time"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"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/utils"
@ -55,7 +56,10 @@ func NewMainMenu(root components.RootModel) tea.Model {
ItemTitle: "Save",
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
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
},
@ -75,6 +79,8 @@ func NewMainMenu(root components.RootModel) tea.Model {
model.list.Title = "Main Menu"
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
}
@ -84,7 +90,6 @@ func (m mainMenu) Init() tea.Cmd {
}
func (m mainMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
log.Warn().Msg(spew.Sdump(msg))
switch msg := msg.(type) {
case tea.KeyMsg:
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, tea.Quit
return m, nil
default:
var cmd tea.Cmd
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
import (
"time"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"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/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) {
err := root.GetCurrentProfile().AddMod(mod.Reference, ">=0.0.0")
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
},
@ -79,6 +83,8 @@ func NewModMenu(root components.RootModel, parent tea.Model, mod utils.Mod) tea.
model.list.Styles = utils.ListStyles
model.list.SetSize(model.list.Width(), model.list.Height())
model.list.KeyMap.Quit.SetHelp("q", "back")
model.list.StatusMessageLifetime = time.Second * 3
model.list.DisableQuitKeybindings()
return model
}
@ -88,7 +94,6 @@ func (m modMenu) Init() tea.Cmd {
}
func (m modMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
log.Warn().Msg(spew.Sdump(msg))
switch msg := msg.(type) {
case tea.KeyMsg:
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, tea.Quit
return m, nil
default:
var cmd tea.Cmd
m.list, cmd = m.list.Update(msg)

View file

@ -5,6 +5,8 @@ import (
"strconv"
"strings"
"github.com/rs/zerolog/log"
"github.com/PuerkitoBio/goquery"
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)
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")
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))

View file

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

View file

@ -1,10 +1,11 @@
package scenes
import (
"time"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"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/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) {
err := root.GetCurrentProfile().AddMod(mod.Reference, ">=0.0.0")
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
},
@ -63,6 +67,8 @@ func NewModVersion(root components.RootModel, parent tea.Model, mod utils.Mod) t
model.list.Styles = utils.ListStyles
model.list.SetSize(model.list.Width(), model.list.Height())
model.list.KeyMap.Quit.SetHelp("q", "back")
model.list.StatusMessageLifetime = time.Second * 3
model.list.DisableQuitKeybindings()
return model
}
@ -72,7 +78,6 @@ func (m modVersionMenu) Init() tea.Cmd {
}
func (m modVersionMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
log.Warn().Msg(spew.Sdump(msg))
switch msg := msg.(type) {
case tea.KeyMsg:
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, tea.Quit
return m, nil
default:
var cmd tea.Cmd
m.list, cmd = m.list.Update(msg)

View file

@ -10,8 +10,6 @@ import (
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"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/tea/components"
"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.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("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{
utils.SimpleItem{
ItemTitle: "Name",
@ -101,6 +108,7 @@ func NewMods(root components.RootModel, parent tea.Model) tea.Model {
sortFieldList.Styles = utils.ListStyles
sortFieldList.SetSize(l.Width(), l.Height())
sortFieldList.KeyMap.Quit.SetHelp("q", "back")
sortFieldList.DisableQuitKeybindings()
sortOrderList := list.NewModel([]list.Item{
utils.SimpleItem{
@ -131,6 +139,7 @@ func NewMods(root components.RootModel, parent tea.Model) tea.Model {
sortOrderList.Styles = utils.ListStyles
sortOrderList.SetSize(l.Width(), l.Height())
sortOrderList.KeyMap.Quit.SetHelp("q", "back")
sortOrderList.DisableQuitKeybindings()
m := &modsList{
root: root,
@ -196,7 +205,9 @@ func (m modsList) Init() 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) {
case tea.KeyMsg:
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
import (
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
"github.com/charmbracelet/bubbles/spinner"
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 = (*profiles)(nil)
type profiles struct {
root components.RootModel
list list.Model
parent 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
}
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"
tea "github.com/charmbracelet/bubbletea"
"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/tea/components"
"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.SetSize(l.Width(), l.Height())
l.KeyMap.Quit.SetHelp("q", "back")
l.DisableQuitKeybindings()
m := &selectModVersionList{
root: root,
@ -93,7 +92,6 @@ func (m selectModVersionList) Init() tea.Cmd {
}
func (m selectModVersionList) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
log.Info().Msg(spew.Sdump(msg))
switch msg := msg.(type) {
case tea.KeyMsg:
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, tea.Quit
return m, nil
default:
var cmd tea.Cmd
m.list, cmd = m.list.Update(msg)

View file

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