e313efdfec
* fix: log by default (ficsit-cli.log in CWD) * chore: update readme with info on code generation * chore: regenerate docs for default log file location * feat: compatibility info state and note display. wip: keybind to switch view modes not working * fix: move render code out to a function, but it still isn't quite working yet * feat: display mod reference below mod name * Fix compat toggle with * Show scroll up/down on quick help * chore: fix merge conflict * chore: run go install mvdan.cc/gofumpt@latest; gofumpt -l -w . * chore: run gci.exe write --skip-generated -s standard -s default -s 'prefix(github.com/satisfactorymodding/ficsit-cli)' -s blank -s dot . * chore: update readme linting info and run golangci-lint --version * fix: log file is defaulted to empty again * fix(#33): update render to return just string * fix(#33): renderModInfo returns only string * fix(#33): reollback func namechange * refactor(#33): remove redundant viewport refresh * refactor(#33): update is not required after setting content * refactor(#33): remove unrequired log * docs(#33): update documentation to latest generated * docs(#33): update cache reference to not contain username * docs(#33): fix local dir references too * refactor(#33): replace vague variable with more helpful * Add directions about using dev schema when generate command fails * Fix issues from earlier merge conflict --------- Co-authored-by: Jack Stupple <jack.stupple@protonmail.com>
306 lines
8.9 KiB
Go
306 lines
8.9 KiB
Go
package mods
|
|
|
|
// cspell:disable
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
md "github.com/JohannesKaufmann/html-to-markdown"
|
|
"github.com/PuerkitoBio/goquery"
|
|
"github.com/charmbracelet/bubbles/help"
|
|
"github.com/charmbracelet/bubbles/key"
|
|
"github.com/charmbracelet/bubbles/spinner"
|
|
"github.com/charmbracelet/bubbles/viewport"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/glamour"
|
|
"github.com/charmbracelet/lipgloss"
|
|
|
|
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
|
"github.com/satisfactorymodding/ficsit-cli/tea/components"
|
|
"github.com/satisfactorymodding/ficsit-cli/tea/scenes/keys"
|
|
"github.com/satisfactorymodding/ficsit-cli/tea/utils"
|
|
)
|
|
|
|
// cspell:enable
|
|
|
|
var _ tea.Model = (*modVersionMenu)(nil)
|
|
|
|
type modInfo struct {
|
|
root components.RootModel
|
|
parent tea.Model
|
|
modData chan ficsit.GetModMod
|
|
modDataCache ficsit.GetModMod
|
|
modError chan string
|
|
error *components.ErrorComponent
|
|
help help.Model
|
|
keys modInfoKeyMap
|
|
viewport viewport.Model
|
|
spinner spinner.Model
|
|
ready bool
|
|
compatViewMode bool
|
|
}
|
|
|
|
type modInfoKeyMap struct {
|
|
Up key.Binding
|
|
UpHalf key.Binding
|
|
UpPage key.Binding
|
|
Down key.Binding
|
|
DownHalf key.Binding
|
|
DownPage key.Binding
|
|
Help key.Binding
|
|
Back key.Binding
|
|
CompatInfo key.Binding
|
|
}
|
|
|
|
func (k modInfoKeyMap) ShortHelp() []key.Binding {
|
|
return []key.Binding{k.Help, k.Back, k.Up, k.Down, k.CompatInfo}
|
|
}
|
|
|
|
func (k modInfoKeyMap) FullHelp() [][]key.Binding {
|
|
return [][]key.Binding{
|
|
{k.Up, k.UpHalf, k.UpPage},
|
|
{k.Down, k.DownHalf, k.DownPage},
|
|
{k.CompatInfo},
|
|
{k.Help, k.Back},
|
|
}
|
|
}
|
|
|
|
func NewModInfo(root components.RootModel, parent tea.Model, mod utils.Mod) tea.Model {
|
|
model := modInfo{
|
|
root: root,
|
|
viewport: viewport.Model{},
|
|
spinner: spinner.New(),
|
|
parent: parent,
|
|
modData: make(chan ficsit.GetModMod),
|
|
modError: make(chan string),
|
|
ready: false,
|
|
help: help.New(),
|
|
keys: modInfoKeyMap{
|
|
Up: key.NewBinding(key.WithKeys("up", "k"), key.WithHelp("↑/k", "move up")),
|
|
UpHalf: key.NewBinding(key.WithKeys("u"), key.WithHelp("u", "up half page")),
|
|
UpPage: key.NewBinding(key.WithKeys("pgup", "b"), key.WithHelp("pgup/b", "page up")),
|
|
Down: key.NewBinding(key.WithKeys("down", "j"), key.WithHelp("↓/j", "move down")),
|
|
DownHalf: key.NewBinding(key.WithKeys("d"), key.WithHelp("d", "down half page")),
|
|
DownPage: key.NewBinding(key.WithKeys("pgdn", "f"), key.WithHelp("pgdn/f", "page down")),
|
|
Help: key.NewBinding(key.WithKeys("?"), key.WithHelp("?", "toggle help")),
|
|
Back: key.NewBinding(key.WithKeys("q"), key.WithHelp("q", "back")),
|
|
CompatInfo: key.NewBinding(key.WithKeys("i"), key.WithHelp("i", "toggle compatibility info view")),
|
|
},
|
|
}
|
|
|
|
model.spinner.Spinner = spinner.MiniDot
|
|
model.help.Width = root.Size().Width
|
|
|
|
go func() {
|
|
fullMod, err := root.GetProvider().GetMod(context.TODO(), mod.Reference)
|
|
if err != nil {
|
|
model.modError <- err.Error()
|
|
return
|
|
}
|
|
|
|
if fullMod == nil {
|
|
model.modError <- "unknown error (mod is nil)"
|
|
return
|
|
}
|
|
|
|
model.modData <- fullMod.Mod
|
|
}()
|
|
|
|
return model
|
|
}
|
|
|
|
func (m modInfo) Init() tea.Cmd {
|
|
return tea.Batch(utils.Ticker(), m.spinner.Tick)
|
|
}
|
|
|
|
func (m modInfo) CalculateSizes(msg tea.WindowSizeMsg) (tea.Model, tea.Cmd) {
|
|
if m.viewport.Width == 0 {
|
|
return m, nil
|
|
}
|
|
|
|
bottomPadding := 2
|
|
if m.help.ShowAll {
|
|
bottomPadding = 4
|
|
}
|
|
|
|
top, right, bottom, left := lipgloss.NewStyle().Margin(m.root.Height(), 3, bottomPadding).GetMargin()
|
|
m.viewport.Width = msg.Width - left - right
|
|
m.viewport.Height = msg.Height - top - bottom
|
|
m.root.SetSize(msg)
|
|
|
|
m.help.Width = m.viewport.Width
|
|
|
|
var cmd tea.Cmd
|
|
m.viewport, cmd = m.viewport.Update(msg)
|
|
return m, cmd
|
|
}
|
|
|
|
func (m modInfo) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
switch msg := msg.(type) {
|
|
case tea.KeyMsg:
|
|
switch keypress := msg.String(); keypress {
|
|
case keys.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 "?":
|
|
m.help.ShowAll = !m.help.ShowAll
|
|
return m.CalculateSizes(m.root.Size())
|
|
case "i":
|
|
m.compatViewMode = !m.compatViewMode
|
|
m.viewport = m.newViewport()
|
|
m.viewport.SetContent(m.renderModInfo())
|
|
return m.CalculateSizes(m.root.Size())
|
|
default:
|
|
break
|
|
}
|
|
|
|
var cmd tea.Cmd
|
|
m.viewport, cmd = m.viewport.Update(msg)
|
|
return m, cmd
|
|
case tea.WindowSizeMsg:
|
|
return m.CalculateSizes(msg)
|
|
case spinner.TickMsg:
|
|
var cmd tea.Cmd
|
|
m.spinner, cmd = m.spinner.Update(msg)
|
|
return m, cmd
|
|
case utils.TickMsg:
|
|
select {
|
|
case mod := <-m.modData:
|
|
m.modDataCache = mod
|
|
m.viewport = m.newViewport()
|
|
m.viewport.SetContent(m.renderModInfo())
|
|
break
|
|
case err := <-m.modError:
|
|
errorComponent, _ := components.NewErrorComponent(err, time.Second*5)
|
|
m.error = errorComponent
|
|
break
|
|
default:
|
|
// skip
|
|
break
|
|
}
|
|
return m, utils.Ticker()
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
func (m modInfo) newViewport() viewport.Model {
|
|
bottomPadding := 2
|
|
if m.help.ShowAll {
|
|
bottomPadding = 4
|
|
}
|
|
|
|
top, right, bottom, left := lipgloss.NewStyle().Margin(m.root.Height(), 3, bottomPadding).GetMargin()
|
|
return viewport.Model{Width: m.root.Size().Width - left - right, Height: m.root.Size().Height - top - bottom}
|
|
}
|
|
|
|
func (m modInfo) renderModInfo() string {
|
|
mod := m.modDataCache
|
|
|
|
title := lipgloss.NewStyle().Padding(0, 2).Render(utils.TitleStyle.Render(mod.Name)) + "\n"
|
|
title += lipgloss.NewStyle().Padding(0, 3).Render("("+string(mod.Mod_reference)+")") + "\n"
|
|
|
|
sidebar := ""
|
|
sidebar += utils.LabelStyle.Render("Views: ") + strconv.Itoa(mod.Views) + "\n"
|
|
sidebar += utils.LabelStyle.Render("Downloads: ") + strconv.Itoa(mod.Downloads) + "\n"
|
|
sidebar += "\n"
|
|
sidebar += utils.LabelStyle.Render("EA Compat: ") + m.renderCompatInfo(mod.Compatibility.EA.State) + "\n"
|
|
sidebar += utils.LabelStyle.Render("EXP Compat: ") + m.renderCompatInfo(mod.Compatibility.EXP.State) + "\n"
|
|
sidebar += "\n"
|
|
sidebar += utils.LabelStyle.Render("Authors:") + "\n"
|
|
|
|
converter := md.NewConverter("", true, nil)
|
|
converter.AddRules(md.Rule{
|
|
Filter: []string{"#text"},
|
|
Replacement: func(content string, selection *goquery.Selection, options *md.Options) *string {
|
|
text := selection.Text()
|
|
return &text
|
|
},
|
|
})
|
|
|
|
for _, author := range mod.Authors {
|
|
sidebar += "\n"
|
|
sidebar += utils.LabelStyle.Render(author.User.Username) + " - " + author.Role
|
|
}
|
|
|
|
description := ""
|
|
if m.compatViewMode {
|
|
a := ""
|
|
a += "Compatibility information is maintained by the community." + "\n"
|
|
a += "If you encounter issues with a mod, please report it on the Discord." + "\n"
|
|
a += "Learn more about what compatibility states mean on ficsit.app" + "\n\n"
|
|
|
|
description = m.renderDescriptionText(a, converter)
|
|
|
|
description += " " + utils.TitleStyle.Render("Early Access Branch Compatibility Note") + "\n"
|
|
description += m.renderDescriptionText(mod.Compatibility.EA.Note, converter)
|
|
description += "\n\n"
|
|
description += " " + utils.TitleStyle.Render("Experimental Branch Compatibility Note") + "\n"
|
|
description += m.renderDescriptionText(mod.Compatibility.EXP.Note, converter)
|
|
} else {
|
|
description += m.renderDescriptionText(mod.Full_description, converter)
|
|
}
|
|
|
|
bottomPart := lipgloss.JoinHorizontal(lipgloss.Top, sidebar, strings.TrimSpace(description))
|
|
|
|
return lipgloss.JoinVertical(lipgloss.Left, title, bottomPart)
|
|
}
|
|
|
|
func (m modInfo) renderDescriptionText(text string, converter *md.Converter) string {
|
|
text = strings.TrimSpace(text)
|
|
if text == "" {
|
|
text = "(No notes provided)"
|
|
}
|
|
|
|
markdownDescription, err := converter.ConvertString(text)
|
|
if err != nil {
|
|
slog.Error("failed to convert html to markdown", slog.Any("err", err))
|
|
markdownDescription = text
|
|
}
|
|
|
|
description, err := glamour.Render(markdownDescription, "dark")
|
|
if err != nil {
|
|
slog.Error("failed to render markdown", slog.Any("err", err))
|
|
description = text
|
|
}
|
|
|
|
return description
|
|
}
|
|
|
|
func (m modInfo) renderCompatInfo(state ficsit.CompatibilityState) string {
|
|
stateText := string(state)
|
|
switch state {
|
|
case ficsit.CompatibilityStateWorks:
|
|
return utils.CompatWorksStyle.Render(stateText)
|
|
case ficsit.CompatibilityStateDamaged:
|
|
return utils.CompatDamagedStyle.Render(stateText)
|
|
case ficsit.CompatibilityStateBroken:
|
|
return utils.CompatBrokenStyle.Render(stateText)
|
|
default:
|
|
return utils.CompatUntestedStyle.Render("Unknown")
|
|
}
|
|
}
|
|
|
|
func (m modInfo) View() string {
|
|
if m.error != nil {
|
|
helpBar := lipgloss.NewStyle().Padding(1, 2).Render(m.help.View(m.keys))
|
|
return lipgloss.JoinVertical(lipgloss.Left, m.root.View(), m.error.View(), m.viewport.View(), helpBar)
|
|
}
|
|
|
|
if m.viewport.Height == 0 {
|
|
spinnerView := lipgloss.NewStyle().Padding(0, 2, 1).Render(m.spinner.View() + " Loading...")
|
|
return lipgloss.JoinVertical(lipgloss.Left, m.root.View(), spinnerView)
|
|
}
|
|
|
|
helpBar := lipgloss.NewStyle().Padding(1, 2).Render(m.help.View(m.keys))
|
|
return lipgloss.JoinVertical(lipgloss.Left, m.root.View(), m.viewport.View(), helpBar)
|
|
}
|