2022-06-03 22:17:02 +00:00
|
|
|
package scenes
|
|
|
|
|
|
|
|
import (
|
2023-12-06 23:39:34 +00:00
|
|
|
"sort"
|
2023-12-13 23:34:01 +00:00
|
|
|
"sync"
|
2023-12-06 23:39:34 +00:00
|
|
|
|
2022-06-03 22:17:02 +00:00
|
|
|
"github.com/charmbracelet/bubbles/progress"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
2023-07-28 11:53:29 +00:00
|
|
|
"github.com/muesli/reflow/wrap"
|
2022-06-18 16:09:09 +00:00
|
|
|
|
2022-06-03 22:17:02 +00:00
|
|
|
"github.com/satisfactorymodding/ficsit-cli/cli"
|
|
|
|
"github.com/satisfactorymodding/ficsit-cli/tea/components"
|
2023-01-16 22:48:14 +00:00
|
|
|
"github.com/satisfactorymodding/ficsit-cli/tea/scenes/keys"
|
2023-12-06 23:39:34 +00:00
|
|
|
teaUtils "github.com/satisfactorymodding/ficsit-cli/tea/utils"
|
|
|
|
"github.com/satisfactorymodding/ficsit-cli/utils"
|
2022-06-03 22:17:02 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var _ tea.Model = (*apply)(nil)
|
|
|
|
|
2023-12-06 23:39:34 +00:00
|
|
|
type modProgress struct {
|
|
|
|
downloadProgress utils.GenericProgress
|
|
|
|
extractProgress utils.GenericProgress
|
|
|
|
downloading bool
|
|
|
|
complete bool
|
|
|
|
}
|
|
|
|
|
|
|
|
type status struct {
|
|
|
|
modProgresses map[string]modProgress
|
|
|
|
installName string
|
|
|
|
overallProgress utils.GenericProgress
|
|
|
|
done bool
|
2022-06-03 22:17:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type apply struct {
|
2023-12-13 23:34:01 +00:00
|
|
|
root components.RootModel
|
|
|
|
parent tea.Model
|
|
|
|
error *components.ErrorComponent
|
|
|
|
updateChannel chan applyUpdate
|
|
|
|
doneChannel chan bool
|
|
|
|
errorChannel chan error
|
|
|
|
cancelChannel chan bool
|
|
|
|
title string
|
|
|
|
status map[string]status
|
|
|
|
overall progress.Model
|
|
|
|
sub progress.Model
|
|
|
|
cancelled bool
|
|
|
|
done bool
|
|
|
|
}
|
|
|
|
|
|
|
|
type applyUpdate struct {
|
|
|
|
Installation *cli.Installation
|
|
|
|
Update cli.InstallUpdate
|
|
|
|
Done bool
|
2022-06-03 22:17:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewApply(root components.RootModel, parent tea.Model) tea.Model {
|
|
|
|
overall := progress.New(progress.WithSolidFill("118"))
|
|
|
|
sub := progress.New(progress.WithSolidFill("202"))
|
|
|
|
|
2023-12-13 23:34:01 +00:00
|
|
|
updateChannel := make(chan applyUpdate)
|
2023-12-06 23:39:34 +00:00
|
|
|
doneChannel := make(chan bool, 1)
|
2022-06-03 22:17:02 +00:00
|
|
|
errorChannel := make(chan error)
|
2022-06-22 22:24:35 +00:00
|
|
|
cancelChannel := make(chan bool, 1)
|
2022-06-03 22:17:02 +00:00
|
|
|
|
|
|
|
model := &apply{
|
2023-12-13 23:34:01 +00:00
|
|
|
root: root,
|
|
|
|
parent: parent,
|
|
|
|
title: teaUtils.NonListTitleStyle.MarginTop(1).MarginBottom(1).Render("Applying Changes"),
|
|
|
|
overall: overall,
|
|
|
|
sub: sub,
|
|
|
|
status: make(map[string]status),
|
|
|
|
updateChannel: updateChannel,
|
|
|
|
doneChannel: doneChannel,
|
|
|
|
errorChannel: errorChannel,
|
|
|
|
cancelChannel: cancelChannel,
|
2022-06-03 22:17:02 +00:00
|
|
|
}
|
|
|
|
|
2023-12-13 23:34:01 +00:00
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
|
|
|
for _, installation := range root.GetGlobal().Installations.Installations {
|
|
|
|
wg.Add(1)
|
|
|
|
|
|
|
|
model.status[installation.Path] = status{
|
|
|
|
modProgresses: make(map[string]modProgress),
|
|
|
|
installName: installation.Path,
|
|
|
|
overallProgress: utils.GenericProgress{},
|
|
|
|
}
|
|
|
|
|
|
|
|
go func(installation *cli.Installation) {
|
|
|
|
defer wg.Done()
|
2022-06-03 22:17:02 +00:00
|
|
|
|
2023-12-06 23:39:34 +00:00
|
|
|
installUpdateChannel := make(chan cli.InstallUpdate)
|
2022-06-03 22:17:02 +00:00
|
|
|
go func() {
|
2023-12-06 23:39:34 +00:00
|
|
|
for update := range installUpdateChannel {
|
2023-12-13 23:34:01 +00:00
|
|
|
updateChannel <- applyUpdate{
|
|
|
|
Installation: installation,
|
|
|
|
Update: update,
|
|
|
|
}
|
2022-06-03 22:17:02 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2023-12-06 23:39:34 +00:00
|
|
|
if err := installation.Install(root.GetGlobal(), installUpdateChannel); err != nil {
|
2022-06-03 22:17:02 +00:00
|
|
|
errorChannel <- err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-12-13 23:34:01 +00:00
|
|
|
updateChannel <- applyUpdate{
|
|
|
|
Installation: installation,
|
|
|
|
Done: true,
|
2022-06-22 22:24:35 +00:00
|
|
|
}
|
2023-12-13 23:34:01 +00:00
|
|
|
}(installation)
|
|
|
|
}
|
2022-06-22 22:24:35 +00:00
|
|
|
|
2023-12-13 23:34:01 +00:00
|
|
|
go func() {
|
|
|
|
wg.Wait()
|
2023-12-06 23:39:34 +00:00
|
|
|
doneChannel <- true
|
2022-06-03 22:17:02 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
return model
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m apply) Init() tea.Cmd {
|
2023-12-06 23:39:34 +00:00
|
|
|
return teaUtils.Ticker()
|
2022-06-03 22:17:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m apply) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
|
|
switch msg := msg.(type) {
|
|
|
|
case tea.KeyMsg:
|
|
|
|
switch keypress := msg.String(); keypress {
|
2023-01-16 22:48:14 +00:00
|
|
|
case keys.KeyControlC:
|
2022-06-03 22:17:02 +00:00
|
|
|
return m, tea.Quit
|
2023-12-07 21:31:37 +00:00
|
|
|
case keys.KeyQ:
|
|
|
|
fallthrough
|
2023-01-16 22:48:14 +00:00
|
|
|
case keys.KeyEscape:
|
2023-12-13 23:34:01 +00:00
|
|
|
if m.done {
|
|
|
|
if m.parent != nil {
|
|
|
|
return m.parent, m.parent.Init()
|
|
|
|
}
|
|
|
|
return m, tea.Quit
|
|
|
|
}
|
|
|
|
|
2022-06-22 22:24:35 +00:00
|
|
|
m.cancelled = true
|
2023-12-07 21:31:37 +00:00
|
|
|
|
|
|
|
if m.error != nil {
|
|
|
|
if m.parent != nil {
|
|
|
|
return m.parent, m.parent.Init()
|
|
|
|
}
|
|
|
|
return m, tea.Quit
|
|
|
|
}
|
|
|
|
|
2022-06-22 22:24:35 +00:00
|
|
|
m.cancelChannel <- true
|
2022-06-03 22:17:02 +00:00
|
|
|
return m, nil
|
2023-01-16 22:48:14 +00:00
|
|
|
case keys.KeyEnter:
|
2023-12-13 23:34:01 +00:00
|
|
|
if m.done || m.error != nil {
|
2022-06-03 22:17:02 +00:00
|
|
|
if m.parent != nil {
|
2022-06-07 23:36:28 +00:00
|
|
|
return m.parent, m.parent.Init()
|
2022-06-03 22:17:02 +00:00
|
|
|
}
|
2023-12-07 21:31:37 +00:00
|
|
|
return m, tea.Quit
|
2022-06-03 22:17:02 +00:00
|
|
|
}
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
case tea.WindowSizeMsg:
|
|
|
|
m.root.SetSize(msg)
|
|
|
|
case components.ErrorComponentTimeoutMsg:
|
|
|
|
m.error = nil
|
2023-12-06 23:39:34 +00:00
|
|
|
case teaUtils.TickMsg:
|
2022-06-03 22:17:02 +00:00
|
|
|
select {
|
2023-12-06 23:39:34 +00:00
|
|
|
case <-m.doneChannel:
|
2023-12-13 23:34:01 +00:00
|
|
|
m.done = true
|
2023-12-06 23:39:34 +00:00
|
|
|
break
|
|
|
|
case update := <-m.updateChannel:
|
2023-12-13 23:34:01 +00:00
|
|
|
s := m.status[update.Installation.Path]
|
|
|
|
|
|
|
|
if update.Done {
|
|
|
|
s.done = true
|
|
|
|
} else {
|
|
|
|
switch update.Update.Type {
|
|
|
|
case cli.InstallUpdateTypeOverall:
|
|
|
|
s.overallProgress = update.Update.Progress
|
|
|
|
case cli.InstallUpdateTypeModDownload:
|
|
|
|
s.modProgresses[update.Update.Item.Mod] = modProgress{
|
|
|
|
downloadProgress: update.Update.Progress,
|
|
|
|
downloading: true,
|
|
|
|
complete: false,
|
|
|
|
}
|
|
|
|
case cli.InstallUpdateTypeModExtract:
|
|
|
|
s.modProgresses[update.Update.Item.Mod] = modProgress{
|
|
|
|
extractProgress: update.Update.Progress,
|
|
|
|
downloading: false,
|
|
|
|
complete: false,
|
|
|
|
}
|
|
|
|
case cli.InstallUpdateTypeModComplete:
|
|
|
|
s.modProgresses[update.Update.Item.Mod] = modProgress{
|
|
|
|
complete: true,
|
|
|
|
}
|
2023-12-06 23:39:34 +00:00
|
|
|
}
|
|
|
|
}
|
2023-12-13 23:34:01 +00:00
|
|
|
|
|
|
|
m.status[update.Installation.Path] = s
|
2022-06-03 22:17:02 +00:00
|
|
|
break
|
|
|
|
case err := <-m.errorChannel:
|
2023-07-28 11:53:29 +00:00
|
|
|
wrappedErrMessage := wrap.String(err.Error(), int(float64(m.root.Size().Width)*0.8))
|
|
|
|
errorComponent, _ := components.NewErrorComponent(wrappedErrMessage, 0)
|
2022-06-03 22:17:02 +00:00
|
|
|
m.error = errorComponent
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
// Skip if nothing there
|
|
|
|
break
|
|
|
|
}
|
2023-12-06 23:39:34 +00:00
|
|
|
return m, teaUtils.Ticker()
|
2022-06-03 22:17:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m apply) View() string {
|
|
|
|
strs := make([]string, 0)
|
|
|
|
|
2023-12-13 23:34:01 +00:00
|
|
|
installationList := make([]string, len(m.status))
|
|
|
|
i := 0
|
|
|
|
for key := range m.status {
|
|
|
|
installationList[i] = key
|
|
|
|
i++
|
2023-12-06 23:39:34 +00:00
|
|
|
}
|
2022-06-03 22:17:02 +00:00
|
|
|
|
2023-12-13 23:34:01 +00:00
|
|
|
sort.Strings(installationList)
|
|
|
|
|
|
|
|
totalHeight := 3 + 3 // Header + Footer
|
|
|
|
totalHeight += len(installationList) * 2 // Bottom Margin + Overall progress per-install
|
|
|
|
|
|
|
|
bottomMargins := 1
|
|
|
|
if m.root.Size().Height < totalHeight {
|
|
|
|
bottomMargins = 0
|
2022-06-03 22:17:02 +00:00
|
|
|
}
|
|
|
|
|
2023-12-13 23:34:01 +00:00
|
|
|
totalHeight += len(installationList) // Top margin
|
|
|
|
|
|
|
|
topMargins := 1
|
|
|
|
if m.root.Size().Height < totalHeight {
|
|
|
|
topMargins = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, installPath := range installationList {
|
|
|
|
totalHeight += len(m.status[installPath].modProgresses)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, installPath := range installationList {
|
|
|
|
s := m.status[installPath]
|
|
|
|
|
2023-12-16 11:59:58 +00:00
|
|
|
strs = append(strs, lipgloss.NewStyle().Margin(topMargins, 0, bottomMargins).Render(lipgloss.JoinHorizontal(
|
2023-12-13 23:34:01 +00:00
|
|
|
lipgloss.Left,
|
|
|
|
m.overall.ViewAs(s.overallProgress.Percentage()),
|
|
|
|
" - ",
|
|
|
|
lipgloss.NewStyle().Render(installPath),
|
|
|
|
)))
|
|
|
|
|
|
|
|
modReferences := make([]string, 0)
|
|
|
|
for k := range s.modProgresses {
|
|
|
|
modReferences = append(modReferences, k)
|
|
|
|
}
|
|
|
|
sort.Strings(modReferences)
|
|
|
|
|
|
|
|
if m.root.Size().Height > totalHeight {
|
|
|
|
for _, modReference := range modReferences {
|
|
|
|
p := s.modProgresses[modReference]
|
|
|
|
if p.complete || s.done {
|
2023-12-16 11:59:58 +00:00
|
|
|
strs = append(strs, lipgloss.NewStyle().MarginLeft(2).Foreground(lipgloss.Color("22")).Render("✓ ")+modReference)
|
2023-12-13 23:34:01 +00:00
|
|
|
} else {
|
|
|
|
if p.downloading {
|
2023-12-16 11:59:58 +00:00
|
|
|
strs = append(strs, lipgloss.NewStyle().MarginLeft(1).Render(lipgloss.JoinHorizontal(
|
2023-12-13 23:34:01 +00:00
|
|
|
lipgloss.Left,
|
|
|
|
m.sub.ViewAs(p.downloadProgress.Percentage()),
|
|
|
|
" - ",
|
|
|
|
lipgloss.NewStyle().Render(modReference+" (Downloading)"),
|
2023-12-16 11:59:58 +00:00
|
|
|
)))
|
2023-12-13 23:34:01 +00:00
|
|
|
} else {
|
2023-12-16 11:59:58 +00:00
|
|
|
strs = append(strs, lipgloss.NewStyle().MarginLeft(1).Render(lipgloss.JoinHorizontal(
|
2023-12-13 23:34:01 +00:00
|
|
|
lipgloss.Left,
|
|
|
|
m.sub.ViewAs(p.extractProgress.Percentage()),
|
|
|
|
" - ",
|
|
|
|
lipgloss.NewStyle().Render(modReference+" (Extracting)"),
|
2023-12-16 11:59:58 +00:00
|
|
|
)))
|
2023-12-13 23:34:01 +00:00
|
|
|
}
|
|
|
|
}
|
2023-12-06 23:39:34 +00:00
|
|
|
}
|
|
|
|
}
|
2022-06-03 22:17:02 +00:00
|
|
|
}
|
|
|
|
|
2023-12-13 23:34:01 +00:00
|
|
|
if m.done {
|
2022-06-22 22:24:35 +00:00
|
|
|
if m.cancelled {
|
2023-12-06 23:39:34 +00:00
|
|
|
strs = append(strs, teaUtils.LabelStyle.Copy().Foreground(lipgloss.Color("196")).Padding(0).Margin(1).Render("Cancelled! Press Enter to return"))
|
2022-06-22 22:24:35 +00:00
|
|
|
} else {
|
2023-12-06 23:39:34 +00:00
|
|
|
strs = append(strs, teaUtils.LabelStyle.Copy().Padding(0).Margin(1).Render("Done! Press Enter to return"))
|
2022-06-22 22:24:35 +00:00
|
|
|
}
|
2022-06-03 22:17:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
result := lipgloss.NewStyle().MarginLeft(1).Render(lipgloss.JoinVertical(lipgloss.Left, strs...))
|
|
|
|
|
|
|
|
if m.error != nil {
|
2022-10-14 16:11:16 +00:00
|
|
|
return lipgloss.JoinVertical(lipgloss.Left, m.title, m.error.View(), result)
|
2022-06-03 22:17:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return lipgloss.JoinVertical(lipgloss.Left, m.title, result)
|
|
|
|
}
|