mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-01 19:18:28 -04:00
330 lines
9.6 KiB
Go
330 lines
9.6 KiB
Go
package tui
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
)
|
|
|
|
func (m Model) viewDetectingDeps() string {
|
|
var b strings.Builder
|
|
|
|
b.WriteString(m.renderBanner())
|
|
b.WriteString("\n")
|
|
|
|
title := m.styles.Title.Render("Detecting Dependencies")
|
|
b.WriteString(title)
|
|
b.WriteString("\n\n")
|
|
|
|
spinner := m.spinner.View()
|
|
status := m.styles.Normal.Render("Scanning system for existing packages and configurations...")
|
|
fmt.Fprintf(&b, "%s %s", spinner, status)
|
|
|
|
return b.String()
|
|
}
|
|
|
|
func partitionOptionalLast(dependencies []deps.Dependency) []deps.Dependency {
|
|
ordered := make([]deps.Dependency, 0, len(dependencies))
|
|
for _, dep := range dependencies {
|
|
if dep.Required {
|
|
ordered = append(ordered, dep)
|
|
}
|
|
}
|
|
for _, dep := range dependencies {
|
|
if !dep.Required {
|
|
ordered = append(ordered, dep)
|
|
}
|
|
}
|
|
return ordered
|
|
}
|
|
|
|
func (m Model) viewDependencyReview() string {
|
|
var b strings.Builder
|
|
|
|
b.WriteString(m.renderBanner())
|
|
b.WriteString("\n")
|
|
|
|
title := m.styles.Title.Render("Dependency Review")
|
|
b.WriteString(title)
|
|
b.WriteString("\n\n")
|
|
|
|
if len(m.dependencies) > 0 {
|
|
optionalHeaderShown := false
|
|
for i, dep := range m.dependencies {
|
|
if !dep.Required && !optionalHeaderShown {
|
|
b.WriteString("\n")
|
|
b.WriteString(m.styles.Subtle.Render("Optional (space to enable)"))
|
|
b.WriteString("\n")
|
|
optionalHeaderShown = true
|
|
}
|
|
|
|
var status string
|
|
var reinstallMarker string
|
|
var variantMarker string
|
|
|
|
isDMS := dep.Name == "dms (DankMaterialShell)"
|
|
|
|
if dep.CanToggle && dep.Variant == deps.VariantGit {
|
|
variantMarker = "[git] "
|
|
}
|
|
|
|
if m.disabledItems[dep.Name] {
|
|
reinstallMarker = "✗ "
|
|
status = m.styles.Subtle.Render("Will skip")
|
|
} else if m.reinstallItems[dep.Name] {
|
|
reinstallMarker = "🔄 "
|
|
status = m.styles.Warning.Render("Will upgrade")
|
|
} else if isDMS {
|
|
reinstallMarker = "⚡ "
|
|
switch dep.Status {
|
|
case deps.StatusInstalled:
|
|
status = m.styles.Success.Render("✓ Required (installed)")
|
|
case deps.StatusMissing:
|
|
status = m.styles.Warning.Render("○ Required (will install)")
|
|
case deps.StatusNeedsUpdate:
|
|
status = m.styles.Warning.Render("△ Required (needs update)")
|
|
case deps.StatusNeedsReinstall:
|
|
status = m.styles.Error.Render("! Required (needs reinstall)")
|
|
}
|
|
} else {
|
|
switch dep.Status {
|
|
case deps.StatusInstalled:
|
|
status = m.styles.Subtle.Render("✓ Already installed")
|
|
case deps.StatusMissing:
|
|
status = m.styles.Warning.Render("○ Will install")
|
|
case deps.StatusNeedsUpdate:
|
|
status = m.styles.Warning.Render("△ Will install")
|
|
case deps.StatusNeedsReinstall:
|
|
status = m.styles.Error.Render("! Will install")
|
|
}
|
|
}
|
|
|
|
note := ""
|
|
switch dep.Name {
|
|
case "dms-greeter":
|
|
note = m.styles.Subtle.Render(" (selection replaces your current display manager)")
|
|
case "danksearch":
|
|
note = m.styles.Subtle.Render(" (file search; enables dsearch.service)")
|
|
case "dankcalendar":
|
|
note = m.styles.Subtle.Render(" (autostart managed in dankcalendar settings)")
|
|
}
|
|
|
|
var line string
|
|
if i == m.selectedDep {
|
|
line = fmt.Sprintf("▶ %s%s%-25s %s", reinstallMarker, variantMarker, dep.Name, status)
|
|
if dep.Version != "" {
|
|
line += fmt.Sprintf(" (%s)", dep.Version)
|
|
}
|
|
line = m.styles.SelectedOption.Render(line) + note
|
|
} else {
|
|
line = fmt.Sprintf(" %s%s%-25s %s", reinstallMarker, variantMarker, dep.Name, status)
|
|
if dep.Version != "" {
|
|
line += fmt.Sprintf(" (%s)", dep.Version)
|
|
}
|
|
line = m.styles.Normal.Render(line) + note
|
|
}
|
|
|
|
b.WriteString(line)
|
|
b.WriteString("\n")
|
|
}
|
|
}
|
|
|
|
b.WriteString("\n")
|
|
help := m.styles.Subtle.Render("↑/↓: Navigate, Space: Toggle, G: Toggle stable/git, Enter: Continue")
|
|
b.WriteString(help)
|
|
|
|
return b.String()
|
|
}
|
|
|
|
func (m Model) updateDetectingDepsState(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
if depsMsg, ok := msg.(depsDetectedMsg); ok {
|
|
m.isLoading = false
|
|
if depsMsg.err != nil {
|
|
m.err = depsMsg.err
|
|
m.state = StateError
|
|
} else {
|
|
m.dependencies = partitionOptionalLast(depsMsg.deps)
|
|
// Optional components are opt-in, skipped by default
|
|
for _, dep := range m.dependencies {
|
|
if dep.Required {
|
|
continue
|
|
}
|
|
m.disabledItems[dep.Name] = true
|
|
}
|
|
m.state = StateDependencyReview
|
|
}
|
|
return m, m.listenForLogs()
|
|
}
|
|
return m, m.listenForLogs()
|
|
}
|
|
|
|
func (m Model) updateDependencyReviewState(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
if keyMsg, ok := msg.(tea.KeyMsg); ok {
|
|
switch keyMsg.String() {
|
|
case "up":
|
|
if m.selectedDep > 0 {
|
|
m.selectedDep--
|
|
}
|
|
case "down":
|
|
if m.selectedDep < len(m.dependencies)-1 {
|
|
m.selectedDep++
|
|
}
|
|
case " ":
|
|
if len(m.dependencies) > 0 {
|
|
depName := m.dependencies[m.selectedDep].Name
|
|
isDMS := depName == "dms (DankMaterialShell)"
|
|
|
|
if !isDMS {
|
|
isInstalled := m.dependencies[m.selectedDep].Status == deps.StatusInstalled ||
|
|
m.dependencies[m.selectedDep].Status == deps.StatusNeedsReinstall
|
|
|
|
if isInstalled {
|
|
m.reinstallItems[depName] = !m.reinstallItems[depName]
|
|
m.disabledItems[depName] = false
|
|
} else {
|
|
m.disabledItems[depName] = !m.disabledItems[depName]
|
|
m.reinstallItems[depName] = false
|
|
}
|
|
}
|
|
}
|
|
case "g", "G":
|
|
if len(m.dependencies) > 0 && m.dependencies[m.selectedDep].CanToggle {
|
|
if m.dependencies[m.selectedDep].Variant == deps.VariantStable {
|
|
m.dependencies[m.selectedDep].Variant = deps.VariantGit
|
|
} else {
|
|
m.dependencies[m.selectedDep].Variant = deps.VariantStable
|
|
}
|
|
}
|
|
case "enter":
|
|
// Check if on Gentoo - show USE flags screen
|
|
if m.osInfo != nil {
|
|
if config, exists := distros.Registry[m.osInfo.Distribution.ID]; exists && config.Family == distros.FamilyGentoo {
|
|
m.state = StateGentooUseFlags
|
|
return m, nil
|
|
}
|
|
}
|
|
return m.enterAuthPhase()
|
|
case "esc":
|
|
m.state = StateSelectWindowManager
|
|
return m, nil
|
|
}
|
|
}
|
|
return m, m.listenForLogs()
|
|
}
|
|
|
|
func (m Model) installPackages() tea.Cmd {
|
|
return func() tea.Msg {
|
|
if m.osInfo == nil {
|
|
return packageInstallProgressMsg{
|
|
progress: 0.0,
|
|
step: "Error: OS info not available",
|
|
isComplete: true,
|
|
}
|
|
}
|
|
|
|
installer, err := distros.NewPackageInstaller(m.osInfo.Distribution.ID, m.logChan)
|
|
if err != nil {
|
|
return packageInstallProgressMsg{
|
|
progress: 0.0,
|
|
step: fmt.Sprintf("Error: %s", err.Error()),
|
|
isComplete: true,
|
|
}
|
|
}
|
|
|
|
// Convert TUI selection to deps enum
|
|
wm := m.selectedWindowManager()
|
|
|
|
installerProgressChan := make(chan distros.InstallProgressMsg, 100)
|
|
|
|
go func() {
|
|
defer close(installerProgressChan)
|
|
err := installer.InstallPackages(context.Background(), m.dependencies, wm, m.sudoPassword, m.reinstallItems, m.disabledItems, m.skipGentooUseFlags, installerProgressChan)
|
|
if err != nil {
|
|
installerProgressChan <- distros.InstallProgressMsg{
|
|
Progress: 0.0,
|
|
Step: fmt.Sprintf("Installation error: %s", err.Error()),
|
|
IsComplete: true,
|
|
Error: err,
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Convert installer messages to TUI messages
|
|
go func() {
|
|
for msg := range installerProgressChan {
|
|
// Run optional greeter setup
|
|
if msg.Phase == distros.PhaseComplete && msg.IsComplete && msg.Error == nil {
|
|
if m.optionalDepSelected("dms-greeter") {
|
|
m.packageProgressChan <- packageInstallProgressMsg{
|
|
progress: 0.92,
|
|
step: "Configuring DMS greeter...",
|
|
logOutput: "Starting automated greeter setup...",
|
|
}
|
|
greeterLogFunc := func(line string) {
|
|
m.packageProgressChan <- packageInstallProgressMsg{
|
|
progress: 0.94,
|
|
step: "Configuring DMS greeter...",
|
|
logOutput: line,
|
|
}
|
|
}
|
|
if err := utils.RunDmsGreeterInstall(m.sudoPassword, greeterLogFunc); err != nil {
|
|
m.packageProgressChan <- packageInstallProgressMsg{
|
|
progress: 0.96,
|
|
step: "Greeter setup warning",
|
|
logOutput: fmt.Sprintf("⚠ Greeter auto-setup warning (non-fatal): %v", err),
|
|
}
|
|
}
|
|
|
|
if m.useSystemdConfig() && m.optionalDepSelected("danksearch") {
|
|
m.packageProgressChan <- packageInstallProgressMsg{
|
|
progress: 0.97,
|
|
step: "Enabling danksearch service...",
|
|
logOutput: "Setting up dsearch.service...",
|
|
}
|
|
dsearchLogFunc := func(line string) {
|
|
m.packageProgressChan <- packageInstallProgressMsg{
|
|
progress: 0.97,
|
|
step: "Enabling danksearch service...",
|
|
logOutput: line,
|
|
}
|
|
}
|
|
if err := distros.SetupDsearchService(context.Background(), dsearchLogFunc); err != nil {
|
|
m.packageProgressChan <- packageInstallProgressMsg{
|
|
progress: 0.98,
|
|
step: "danksearch service warning",
|
|
logOutput: fmt.Sprintf("danksearch service setup warning (non-fatal): %v", err),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
tuiMsg := packageInstallProgressMsg{
|
|
progress: msg.Progress,
|
|
step: msg.Step,
|
|
isComplete: msg.IsComplete,
|
|
needsSudo: msg.NeedsSudo,
|
|
commandInfo: msg.CommandInfo,
|
|
logOutput: msg.LogOutput,
|
|
error: msg.Error,
|
|
}
|
|
if msg.IsComplete {
|
|
m.logChan <- fmt.Sprintf("[DEBUG] Sending completion signal: step=%s, progress=%.2f", msg.Step, msg.Progress)
|
|
}
|
|
m.packageProgressChan <- tuiMsg
|
|
}
|
|
m.logChan <- "[DEBUG] Installer channel closed"
|
|
}()
|
|
|
|
return packageInstallProgressMsg{
|
|
progress: 0.05,
|
|
step: "Starting installation...",
|
|
isComplete: false,
|
|
}
|
|
}
|
|
}
|