mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-06 05:25:41 -05:00
530 lines
14 KiB
Go
530 lines
14 KiB
Go
//go:build !distro_binary
|
|
|
|
package dms
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
func (m Model) renderUpdateView() string {
|
|
var b strings.Builder
|
|
|
|
b.WriteString(m.renderBanner())
|
|
b.WriteString("\n")
|
|
|
|
headerStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#FFFFFF")).
|
|
Bold(true).
|
|
MarginBottom(1)
|
|
|
|
b.WriteString(headerStyle.Render("Update Dependencies"))
|
|
b.WriteString("\n")
|
|
|
|
if len(m.updateDeps) == 0 {
|
|
b.WriteString("Loading dependencies...\n")
|
|
return b.String()
|
|
}
|
|
|
|
categories := m.categorizeDependencies()
|
|
currentIndex := 0
|
|
|
|
for _, category := range []string{"Shell", "Shared Components", "Hyprland Components", "Niri Components"} {
|
|
deps, exists := categories[category]
|
|
if !exists || len(deps) == 0 {
|
|
continue
|
|
}
|
|
|
|
categoryStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#7060ac")).
|
|
Bold(true).
|
|
MarginTop(1)
|
|
|
|
b.WriteString(categoryStyle.Render(category + ":"))
|
|
b.WriteString("\n")
|
|
|
|
for _, dep := range deps {
|
|
var statusText, icon, reinstallMarker string
|
|
var style lipgloss.Style
|
|
|
|
if m.updateToggles[dep.Name] {
|
|
reinstallMarker = "🔄 "
|
|
if dep.Status == 0 {
|
|
statusText = "Will be installed"
|
|
} else {
|
|
statusText = "Will be upgraded"
|
|
}
|
|
style = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFA500"))
|
|
} else {
|
|
switch dep.Status {
|
|
case 1:
|
|
icon = "✓"
|
|
statusText = "Installed"
|
|
style = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFFFFF"))
|
|
case 0:
|
|
icon = "○"
|
|
statusText = "Not installed"
|
|
style = lipgloss.NewStyle().Foreground(lipgloss.Color("#888888"))
|
|
case 2:
|
|
icon = "△"
|
|
statusText = "Needs update"
|
|
style = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFA500"))
|
|
case 3:
|
|
icon = "!"
|
|
statusText = "Needs reinstall"
|
|
style = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFA500"))
|
|
}
|
|
}
|
|
|
|
line := fmt.Sprintf("%s%s%-25s %s", reinstallMarker, icon, dep.Name, statusText)
|
|
|
|
if currentIndex == m.selectedUpdateDep {
|
|
line = "▶ " + line
|
|
selectedStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#7060ac")).Bold(true)
|
|
b.WriteString(selectedStyle.Render(line))
|
|
} else {
|
|
line = " " + line
|
|
b.WriteString(style.Render(line))
|
|
}
|
|
b.WriteString("\n")
|
|
currentIndex++
|
|
}
|
|
}
|
|
|
|
b.WriteString("\n")
|
|
instructionStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#888888")).
|
|
MarginTop(1)
|
|
|
|
instructions := "↑/↓: Navigate, Space: Toggle, Enter: Update Selected, Esc: Back"
|
|
b.WriteString(instructionStyle.Render(instructions))
|
|
|
|
return b.String()
|
|
}
|
|
|
|
func (m Model) renderPasswordView() string {
|
|
var b strings.Builder
|
|
|
|
b.WriteString(m.renderBanner())
|
|
b.WriteString("\n")
|
|
|
|
headerStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#FFFFFF")).
|
|
Bold(true).
|
|
MarginBottom(1)
|
|
|
|
b.WriteString(headerStyle.Render("Sudo Authentication"))
|
|
b.WriteString("\n\n")
|
|
|
|
normalStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#FFFFFF"))
|
|
|
|
b.WriteString(normalStyle.Render("Package installation requires sudo privileges."))
|
|
b.WriteString("\n")
|
|
b.WriteString(normalStyle.Render("Please enter your password to continue:"))
|
|
b.WriteString("\n\n")
|
|
|
|
inputStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#00D4AA"))
|
|
|
|
maskedPassword := strings.Repeat("*", len(m.passwordInput))
|
|
b.WriteString(inputStyle.Render("Password: " + maskedPassword))
|
|
b.WriteString("\n")
|
|
|
|
if m.passwordError != "" {
|
|
errorStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#FF0000"))
|
|
b.WriteString(errorStyle.Render("✗ " + m.passwordError))
|
|
b.WriteString("\n")
|
|
}
|
|
|
|
b.WriteString("\n")
|
|
instructionStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#888888")).
|
|
MarginTop(1)
|
|
|
|
instructions := "Enter: Continue, Esc: Back, Ctrl+C: Cancel"
|
|
b.WriteString(instructionStyle.Render(instructions))
|
|
|
|
return b.String()
|
|
}
|
|
|
|
func (m Model) renderProgressView() string {
|
|
var b strings.Builder
|
|
|
|
b.WriteString(m.renderBanner())
|
|
b.WriteString("\n")
|
|
|
|
headerStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#FFFFFF")).
|
|
Bold(true).
|
|
MarginBottom(1)
|
|
|
|
b.WriteString(headerStyle.Render("Updating Packages"))
|
|
b.WriteString("\n\n")
|
|
|
|
if !m.updateProgress.complete {
|
|
progressStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#00D4AA"))
|
|
|
|
b.WriteString(progressStyle.Render(m.updateProgress.step))
|
|
b.WriteString("\n\n")
|
|
|
|
progressBar := fmt.Sprintf("[%s%s] %.0f%%",
|
|
strings.Repeat("█", int(m.updateProgress.progress*30)),
|
|
strings.Repeat("░", 30-int(m.updateProgress.progress*30)),
|
|
m.updateProgress.progress*100)
|
|
b.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("#FFFFFF")).Render(progressBar))
|
|
b.WriteString("\n")
|
|
|
|
if len(m.updateLogs) > 0 {
|
|
b.WriteString("\n")
|
|
logHeader := lipgloss.NewStyle().Foreground(lipgloss.Color("#888888")).Render("Live Output:")
|
|
b.WriteString(logHeader)
|
|
b.WriteString("\n")
|
|
|
|
maxLines := 8
|
|
startIdx := 0
|
|
if len(m.updateLogs) > maxLines {
|
|
startIdx = len(m.updateLogs) - maxLines
|
|
}
|
|
|
|
logStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#888888"))
|
|
for i := startIdx; i < len(m.updateLogs); i++ {
|
|
if m.updateLogs[i] != "" {
|
|
b.WriteString(logStyle.Render(" " + m.updateLogs[i]))
|
|
b.WriteString("\n")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if m.updateProgress.err != nil {
|
|
errorStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#FF0000"))
|
|
|
|
b.WriteString("\n")
|
|
b.WriteString(errorStyle.Render(fmt.Sprintf("✗ Update failed: %v", m.updateProgress.err)))
|
|
b.WriteString("\n")
|
|
|
|
if len(m.updateLogs) > 0 {
|
|
b.WriteString("\n")
|
|
logHeader := lipgloss.NewStyle().Foreground(lipgloss.Color("#888888")).Render("Error Logs:")
|
|
b.WriteString(logHeader)
|
|
b.WriteString("\n")
|
|
|
|
maxLines := 15
|
|
startIdx := 0
|
|
if len(m.updateLogs) > maxLines {
|
|
startIdx = len(m.updateLogs) - maxLines
|
|
}
|
|
|
|
logStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#888888"))
|
|
for i := startIdx; i < len(m.updateLogs); i++ {
|
|
if m.updateLogs[i] != "" {
|
|
b.WriteString(logStyle.Render(" " + m.updateLogs[i]))
|
|
b.WriteString("\n")
|
|
}
|
|
}
|
|
}
|
|
|
|
b.WriteString("\n")
|
|
instructionStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#888888"))
|
|
b.WriteString(instructionStyle.Render("Press Esc to go back"))
|
|
} else if m.updateProgress.complete {
|
|
successStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#00D4AA"))
|
|
|
|
b.WriteString("\n")
|
|
b.WriteString(successStyle.Render("✓ Update complete!"))
|
|
b.WriteString("\n\n")
|
|
|
|
instructionStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#888888"))
|
|
b.WriteString(instructionStyle.Render("Press Esc to return to main menu"))
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
func (m Model) getFilteredDeps() []DependencyInfo {
|
|
categories := m.categorizeDependencies()
|
|
var filtered []DependencyInfo
|
|
|
|
for _, category := range []string{"Shell", "Shared Components", "Hyprland Components", "Niri Components"} {
|
|
deps, exists := categories[category]
|
|
if exists {
|
|
filtered = append(filtered, deps...)
|
|
}
|
|
}
|
|
|
|
return filtered
|
|
}
|
|
|
|
func (m Model) getDepAtVisualIndex(index int) *DependencyInfo {
|
|
filtered := m.getFilteredDeps()
|
|
if index >= 0 && index < len(filtered) {
|
|
return &filtered[index]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m Model) renderGreeterPasswordView() string {
|
|
var b strings.Builder
|
|
|
|
b.WriteString(m.renderBanner())
|
|
b.WriteString("\n")
|
|
|
|
headerStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#FFFFFF")).
|
|
Bold(true).
|
|
MarginBottom(1)
|
|
|
|
b.WriteString(headerStyle.Render("Sudo Authentication"))
|
|
b.WriteString("\n\n")
|
|
|
|
normalStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#FFFFFF"))
|
|
|
|
b.WriteString(normalStyle.Render("Greeter installation requires sudo privileges."))
|
|
b.WriteString("\n")
|
|
b.WriteString(normalStyle.Render("Please enter your password to continue:"))
|
|
b.WriteString("\n\n")
|
|
|
|
inputStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#00D4AA"))
|
|
|
|
maskedPassword := strings.Repeat("*", len(m.greeterPasswordInput))
|
|
b.WriteString(inputStyle.Render("Password: " + maskedPassword))
|
|
b.WriteString("\n")
|
|
|
|
if m.greeterPasswordError != "" {
|
|
errorStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#FF0000"))
|
|
b.WriteString(errorStyle.Render("✗ " + m.greeterPasswordError))
|
|
b.WriteString("\n")
|
|
}
|
|
|
|
b.WriteString("\n")
|
|
instructionStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#888888")).
|
|
MarginTop(1)
|
|
|
|
instructions := "Enter: Continue, Esc: Back, Ctrl+C: Cancel"
|
|
b.WriteString(instructionStyle.Render(instructions))
|
|
|
|
return b.String()
|
|
}
|
|
|
|
func (m Model) renderGreeterCompositorSelect() string {
|
|
var b strings.Builder
|
|
|
|
b.WriteString(m.renderBanner())
|
|
b.WriteString("\n")
|
|
|
|
headerStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#FFFFFF")).
|
|
Bold(true).
|
|
MarginBottom(1)
|
|
|
|
b.WriteString(headerStyle.Render("Select Compositor"))
|
|
b.WriteString("\n\n")
|
|
|
|
normalStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#FFFFFF"))
|
|
|
|
b.WriteString(normalStyle.Render("Multiple compositors detected. Choose which one to use for the greeter:"))
|
|
b.WriteString("\n\n")
|
|
|
|
selectedStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#00D4AA")).
|
|
Bold(true)
|
|
|
|
for i, comp := range m.greeterCompositors {
|
|
if i == m.greeterSelectedComp {
|
|
b.WriteString(selectedStyle.Render(fmt.Sprintf("▶ %s", comp)))
|
|
} else {
|
|
b.WriteString(normalStyle.Render(fmt.Sprintf(" %s", comp)))
|
|
}
|
|
b.WriteString("\n")
|
|
}
|
|
|
|
b.WriteString("\n")
|
|
instructionStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#888888")).
|
|
MarginTop(1)
|
|
|
|
instructions := "↑/↓: Navigate, Enter: Select, Esc: Back"
|
|
b.WriteString(instructionStyle.Render(instructions))
|
|
|
|
return b.String()
|
|
}
|
|
|
|
func (m Model) renderGreeterMenu() string {
|
|
var b strings.Builder
|
|
|
|
b.WriteString(m.renderBanner())
|
|
b.WriteString("\n")
|
|
|
|
headerStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#FFFFFF")).
|
|
Bold(true).
|
|
MarginBottom(1)
|
|
|
|
b.WriteString(headerStyle.Render("Greeter Management"))
|
|
b.WriteString("\n")
|
|
|
|
greeterMenuItems := []string{"Install Greeter"}
|
|
|
|
selectedStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#00D4AA")).
|
|
Bold(true)
|
|
|
|
normalStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#FFFFFF"))
|
|
|
|
for i, item := range greeterMenuItems {
|
|
if i == m.selectedGreeterItem {
|
|
b.WriteString(selectedStyle.Render(fmt.Sprintf("▶ %s", item)))
|
|
} else {
|
|
b.WriteString(normalStyle.Render(fmt.Sprintf(" %s", item)))
|
|
}
|
|
b.WriteString("\n")
|
|
}
|
|
|
|
b.WriteString("\n")
|
|
instructionStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#888888")).
|
|
MarginTop(1)
|
|
|
|
instructions := "↑/↓: Navigate, Enter: Select, Esc: Back"
|
|
b.WriteString(instructionStyle.Render(instructions))
|
|
|
|
return b.String()
|
|
}
|
|
|
|
func (m Model) renderGreeterInstalling() string {
|
|
var b strings.Builder
|
|
|
|
b.WriteString(m.renderBanner())
|
|
b.WriteString("\n")
|
|
|
|
headerStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#FFFFFF")).
|
|
Bold(true).
|
|
MarginBottom(1)
|
|
|
|
b.WriteString(headerStyle.Render("Installing Greeter"))
|
|
b.WriteString("\n\n")
|
|
|
|
if !m.greeterProgress.complete {
|
|
progressStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#00D4AA"))
|
|
|
|
b.WriteString(progressStyle.Render(m.greeterProgress.step))
|
|
b.WriteString("\n\n")
|
|
|
|
if len(m.greeterLogs) > 0 {
|
|
b.WriteString("\n")
|
|
logHeader := lipgloss.NewStyle().Foreground(lipgloss.Color("#888888")).Render("Output:")
|
|
b.WriteString(logHeader)
|
|
b.WriteString("\n")
|
|
|
|
maxLines := 10
|
|
startIdx := 0
|
|
if len(m.greeterLogs) > maxLines {
|
|
startIdx = len(m.greeterLogs) - maxLines
|
|
}
|
|
|
|
logStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#888888"))
|
|
for i := startIdx; i < len(m.greeterLogs); i++ {
|
|
if m.greeterLogs[i] != "" {
|
|
b.WriteString(logStyle.Render(" " + m.greeterLogs[i]))
|
|
b.WriteString("\n")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if m.greeterProgress.err != nil {
|
|
errorStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#FF0000"))
|
|
|
|
b.WriteString("\n")
|
|
b.WriteString(errorStyle.Render(fmt.Sprintf("✗ Installation failed: %v", m.greeterProgress.err)))
|
|
b.WriteString("\n\n")
|
|
|
|
instructionStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#888888"))
|
|
b.WriteString(instructionStyle.Render("Press Esc to go back"))
|
|
} else if m.greeterProgress.complete {
|
|
successStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#00D4AA"))
|
|
|
|
b.WriteString("\n")
|
|
b.WriteString(successStyle.Render("✓ Greeter installation complete!"))
|
|
b.WriteString("\n\n")
|
|
|
|
normalStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#FFFFFF"))
|
|
|
|
b.WriteString(normalStyle.Render("To test the greeter, run:"))
|
|
b.WriteString("\n")
|
|
b.WriteString(normalStyle.Render(" sudo systemctl start greetd"))
|
|
b.WriteString("\n\n")
|
|
b.WriteString(normalStyle.Render("To enable on boot, run:"))
|
|
b.WriteString("\n")
|
|
b.WriteString(normalStyle.Render(" sudo systemctl enable --now greetd"))
|
|
b.WriteString("\n\n")
|
|
|
|
instructionStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#888888"))
|
|
b.WriteString(instructionStyle.Render("Press Esc to return to main menu"))
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
func (m Model) categorizeDependencies() map[string][]DependencyInfo {
|
|
categories := map[string][]DependencyInfo{
|
|
"Shell": {},
|
|
"Shared Components": {},
|
|
"Hyprland Components": {},
|
|
"Niri Components": {},
|
|
}
|
|
|
|
excludeList := map[string]bool{
|
|
"git": true,
|
|
"polkit-agent": true,
|
|
"jq": true,
|
|
"xdg-desktop-portal": true,
|
|
"xdg-desktop-portal-wlr": true,
|
|
"xdg-desktop-portal-hyprland": true,
|
|
"xdg-desktop-portal-gtk": true,
|
|
}
|
|
|
|
for _, dep := range m.updateDeps {
|
|
if excludeList[dep.Name] {
|
|
continue
|
|
}
|
|
|
|
switch dep.Name {
|
|
case "dms (DankMaterialShell)", "quickshell":
|
|
categories["Shell"] = append(categories["Shell"], dep)
|
|
case "hyprland", "grim", "slurp", "hyprctl", "grimblast":
|
|
categories["Hyprland Components"] = append(categories["Hyprland Components"], dep)
|
|
case "niri":
|
|
categories["Niri Components"] = append(categories["Niri Components"], dep)
|
|
case "kitty", "alacritty", "ghostty", "hyprpicker":
|
|
categories["Shared Components"] = append(categories["Shared Components"], dep)
|
|
default:
|
|
categories["Shared Components"] = append(categories["Shared Components"], dep)
|
|
}
|
|
}
|
|
|
|
return categories
|
|
}
|