diff --git a/internal/cmd/import.go b/internal/cmd/import.go index e23f9f5..4908b7e 100644 --- a/internal/cmd/import.go +++ b/internal/cmd/import.go @@ -5,8 +5,8 @@ import ( "devctl/internal/config" "devctl/internal/formats" "devctl/internal/ui" - "devctl/internal/ui/component" "devctl/internal/ui/view" + "devctl/internal/ui/widgets" "devctl/pkg/pkgmgr" "devctl/pkg/pkgmgr/scoop" "devctl/pkg/version" @@ -63,7 +63,7 @@ func runImport(cfg *config.Config, filePath string) error { } // Show spinner during cache building phase - prepSpinner := component.NewSpinner(output) + prepSpinner := widgets.NewSpinner(output) prepSpinner.Start("Preparing context") managerCache := make(map[pkgmgr.ManagerType]pkgmgr.Manager) diff --git a/internal/cmd/init.go b/internal/cmd/init/init.go similarity index 60% rename from internal/cmd/init.go rename to internal/cmd/init/init.go index 40cba5c..ba91361 100644 --- a/internal/cmd/init.go +++ b/internal/cmd/init/init.go @@ -1,10 +1,11 @@ -package cmd +package init import ( "context" "devctl/internal/config" "devctl/internal/installer" "devctl/internal/ui" + "devctl/internal/ui/widgets" "devctl/pkg/executil" "devctl/pkg/pkgmgr" "fmt" @@ -14,7 +15,7 @@ import ( "github.com/spf13/cobra" ) -func NewCmdInit(cfg *config.Config) *cobra.Command { +func NewCmd(cfg *config.Config) *cobra.Command { cmd := &cobra.Command{ Use: "init", Short: "Initialize configuration by detecting package managers", @@ -28,36 +29,39 @@ func NewCmdInit(cfg *config.Config) *cobra.Command { } func runInit(cfg *config.Config) error { - out := ui.NewDefaultOutput() + output := ui.NewDefaultOutput() currentPlatform := pkgmgr.GetCurrent() detectResult := detectPackageManagers(currentPlatform) - displayDetectionResults(out, detectResult, currentPlatform) + displayDetectionResults(output, detectResult, currentPlatform) uninstalled := getUninstalledManagers(detectResult) if len(uninstalled) == 0 { - out.Println("") - return saveConfiguration(out, cfg, detectResult) + output.Println("") + return saveConfiguration(output, cfg, detectResult) } - out.Println("") - confirmed, err := ui.ConfirmAutoInstall(len(uninstalled)) + output.Println("") + confirmed, err := widgets.NewConfirmForm(widgets.ConfirmFormConfig{ + Title: fmt.Sprintf("Found %d uninstalled package manager(s). Install automatically?", len(uninstalled)), + Desc: "This will execute installation scripts on your system.", + }) if err != nil { return fmt.Errorf("failed to get user confirmation: %w", err) } if !confirmed { - out.Println("\nManual installation guides:") + output.Println("\nManual installation guides:") for _, mgr := range uninstalled { - showManualInstallGuide(out, mgr.Type, string(currentPlatform)) + showManualInstallGuide(output, mgr.Type, string(currentPlatform)) } - out.Println("") - return saveConfiguration(out, cfg, detectResult) + output.Println("") + return saveConfiguration(output, cfg, detectResult) } for _, mgr := range uninstalled { - if err := attemptAutoInstall(out, mgr.Type, string(currentPlatform), cfg.Debug); err != nil { - out.Error(fmt.Sprintf("Failed to install %s: %v", mgr.Type, err)) + if err := attemptAutoInstall(output, mgr.Type, string(currentPlatform), cfg.Debug); err != nil { + output.Error(fmt.Sprintf("Failed to install %s: %v", mgr.Type, err)) continue } @@ -71,8 +75,8 @@ func runInit(cfg *config.Config) error { } } - out.Println("") - return saveConfiguration(out, cfg, detectResult) + output.Println("") + return saveConfiguration(output, cfg, detectResult) } type PackageManagerInfo struct { @@ -98,17 +102,17 @@ func detectPackageManagers(p pkgmgr.Platform) map[pkgmgr.ManagerType]PackageMana return managers } -func displayDetectionResults(out ui.Output, results map[pkgmgr.ManagerType]PackageManagerInfo, p pkgmgr.Platform) { - managers := make([]ui.ManagerStatus, 0, len(results)) +func displayDetectionResults(output ui.Output, results map[pkgmgr.ManagerType]PackageManagerInfo, p pkgmgr.Platform) { + managers := make([]ManagerStatus, 0, len(results)) for _, mgr := range results { - managers = append(managers, ui.ManagerStatus{ + managers = append(managers, ManagerStatus{ Name: string(mgr.Type), Installed: mgr.Installed, Path: mgr.ExecutablePath, }) } - out.PrintDetectionResults(ui.DetectionResult{ + printDetectionResults(output, DetectionResult{ Platform: string(p), Managers: managers, }) @@ -146,7 +150,7 @@ func saveConfiguration(out ui.Output, cfg *config.Config, results map[pkgmgr.Man return nil } -func attemptAutoInstall(out ui.Output, managerType pkgmgr.ManagerType, platformStr string, debug bool) error { +func attemptAutoInstall(output ui.Output, managerType pkgmgr.ManagerType, platformStr string, debug bool) error { inst := installer.GetInstaller(managerType) if inst == nil { return fmt.Errorf("no installer available for %s", managerType) @@ -154,39 +158,44 @@ func attemptAutoInstall(out ui.Output, managerType pkgmgr.ManagerType, platformS canAuto, err := inst.CanAutoInstall() if !canAuto { - out.Error(fmt.Sprintf("%s: Automatic installation not available", managerType)) + output.Error(fmt.Sprintf("%s: Automatic installation not available", managerType)) failedPrereqs := getFailedPrereqs(inst.GetPrerequisites()) if len(failedPrereqs) > 0 { - out.PrintPrerequisites(failedPrereqs) - out.Println("") + printPrerequisites(output, failedPrereqs) + output.Println("") } - showGuide, _ := ui.ConfirmShowGuide() + showGuide, _ := widgets.NewConfirmForm(widgets.ConfirmFormConfig{ + Title: "Show manual installation guide?", + }) if showGuide { - showManualInstallGuide(out, managerType, platformStr) + showManualInstallGuide(output, managerType, platformStr) } return fmt.Errorf("automatic installation not supported: %w", err) } - out.Info(fmt.Sprintf("Installing %s...", managerType)) + output.Info(fmt.Sprintf("Installing %s...", managerType)) cmd := inst.GetInstallCommand() slog.Debug("installer command", slog.String("manager", string(managerType)), slog.String("cmd", cmd)) if debug { - out.PrintInstallCommand(cmd) + output.Printf("\nCommand to execute:\n %s\n\n", ui.DefaultStyles.Info.Render(cmd)) } failedPrereqs := getFailedPrereqs(inst.GetPrerequisites()) if len(failedPrereqs) > 0 { - out.Error(fmt.Sprintf("%s: prerequisites not met for automatic installation", managerType)) - out.PrintPrerequisites(failedPrereqs) - out.Println("") - showManualInstallGuide(out, managerType, platformStr) + output.Error(fmt.Sprintf("%s: prerequisites not met for automatic installation", managerType)) + printPrerequisites(output, failedPrereqs) + output.Println("") + showManualInstallGuide(output, managerType, platformStr) return fmt.Errorf("prerequisites not met for %s", managerType) } - confirmed, err := ui.ConfirmProceed(string(managerType)) + confirmed, err := widgets.NewConfirmForm(widgets.ConfirmFormConfig{ + Title: fmt.Sprintf("Proceed with %s installation?", string(managerType)), + Desc: "This will modify your system PATH and configuration.", + }) if err != nil || !confirmed { return fmt.Errorf("installation cancelled by user") } @@ -202,26 +211,26 @@ func attemptAutoInstall(out ui.Output, managerType pkgmgr.ManagerType, platformS }() for progress := range progressChan { - out.PrintInstallProgress(progress.Stage, progress.Message) + output.Printf("%s [%s] %s\n", ui.DefaultStyles.Info.Render(ui.IconInfo), progress.Stage, progress.Message) } if err := <-errChan; err != nil { - out.Error("Installation failed") - showManualInstallGuide(out, managerType, platformStr) + output.Error("Installation failed") + showManualInstallGuide(output, managerType, platformStr) return err } - out.Success(fmt.Sprintf("%s installed successfully!", managerType)) + output.Success(fmt.Sprintf("%s installed successfully!", managerType)) return nil } -func getFailedPrereqs(prereqs []installer.Prerequisite) []ui.PrerequisiteResult { - failed := make([]ui.PrerequisiteResult, 0, len(prereqs)) +func getFailedPrereqs(prereqs []installer.Prerequisite) []PrerequisiteResult { + failed := make([]PrerequisiteResult, 0, len(prereqs)) for _, prereq := range prereqs { if prereq.Passed { continue } - failed = append(failed, ui.PrerequisiteResult{ + failed = append(failed, PrerequisiteResult{ Name: prereq.Name, Passed: prereq.Passed, Message: prereq.Message, @@ -230,14 +239,14 @@ func getFailedPrereqs(prereqs []installer.Prerequisite) []ui.PrerequisiteResult return failed } -func showManualInstallGuide(out ui.Output, managerType pkgmgr.ManagerType, platformStr string) { +func showManualInstallGuide(output ui.Output, managerType pkgmgr.ManagerType, platformStr string) { guide := installer.GetInstallGuide(managerType, platformStr) if guide == nil { - out.Printf("No installation guide available for %s\n", managerType) + output.Printf("No installation guide available for %s\n", managerType) return } - out.PrintManualGuide(ui.ManualGuide{ + printManualGuide(output, ManualGuide{ ManagerName: string(managerType), Instructions: guide.Instructions, URL: guide.URL, diff --git a/internal/cmd/init/ui.go b/internal/cmd/init/ui.go new file mode 100644 index 0000000..9d4e537 --- /dev/null +++ b/internal/cmd/init/ui.go @@ -0,0 +1,91 @@ +package init + +import ( + "devctl/internal/ui" + "fmt" +) + +// DetectionResult represents package manager detection results. +type DetectionResult struct { + Platform string + Managers []ManagerStatus +} + +// ManagerStatus represents the status of a single package manager. +type ManagerStatus struct { + Name string + Installed bool + Path string +} + +// ManualGuide represents manual installation instructions. +type ManualGuide struct { + ManagerName string + Instructions []string + URL string + VerifyCmd string +} + +// PrerequisiteResult represents a prerequisite check result. +type PrerequisiteResult struct { + Name string + Passed bool + Message string +} + +// printDetectionResults displays package manager detection results. +func printDetectionResults(output ui.Output, result DetectionResult) { + output.Printf("\n%s\n", ui.DefaultStyles.Title.Render(fmt.Sprintf("Package Manager Detection (%s)", result.Platform))) + output.Printf("%s\n", ui.Separator(50)) + + for _, mgr := range result.Managers { + if mgr.Installed { + output.Printf("%s %-10s Installed at: %s\n", + ui.DefaultStyles.Success.Render(ui.IconSuccess), + mgr.Name, + mgr.Path) + } else { + output.Printf("%s %-10s Not installed\n", + ui.DefaultStyles.Error.Render(ui.IconError), + mgr.Name) + } + } +} + +// printPrerequisites displays prerequisite check results. +func printPrerequisites(output ui.Output, prereqs []PrerequisiteResult) { + if len(prereqs) == 0 { + return + } + output.Println("Prerequisites:") + for _, prereq := range prereqs { + status := ui.DefaultStyles.Success.Render(ui.IconSuccess) + if !prereq.Passed { + status = ui.DefaultStyles.Error.Render(ui.IconError) + } + output.Printf(" %s %s: %s\n", status, prereq.Name, prereq.Message) + } +} + +// printManualGuide displays manual installation instructions. +func printManualGuide(output ui.Output, guide ManualGuide) { + output.Printf("\n%s Manual Installation Guide for %s\n", + ui.DefaultStyles.Title.Render("📖"), + guide.ManagerName) + + output.Printf("%s\n", ui.Separator(50)) + + for i, instruction := range guide.Instructions { + output.Printf("%d. %s\n", i+1, instruction) + } + + if guide.URL != "" { + output.Printf("\nMore info: %s\n", ui.DefaultStyles.Info.Render(guide.URL)) + } + + if guide.VerifyCmd != "" { + output.Printf("Verify installation: %s\n", ui.DefaultStyles.Info.Render(guide.VerifyCmd)) + } + + output.Println("") +} diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 3aad9a1..3f7d98d 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -1,6 +1,7 @@ package cmd import ( + initcmd "devctl/internal/cmd/init" "devctl/internal/config" "devctl/internal/logging" "devctl/pkg/cmdutil" @@ -30,7 +31,7 @@ func NewCmdRoot() (*cobra.Command, error) { cmd.SetFlagErrorFunc(rootFlagErrorFunc) - cmd.AddCommand(NewCmdInit(cfg)) + cmd.AddCommand(initcmd.NewCmd(cfg)) cmd.AddCommand(NewCmdImport(cfg)) cmd.AddCommand(NewCmdExport(cfg)) cmd.AddCommand(NewCmdSync(cfg)) diff --git a/internal/cmd/sync.go b/internal/cmd/sync.go index f34b081..20ddc0d 100644 --- a/internal/cmd/sync.go +++ b/internal/cmd/sync.go @@ -4,7 +4,7 @@ import ( "context" "devctl/internal/config" "devctl/internal/ui" - "devctl/internal/ui/component" + "devctl/internal/ui/widgets" "devctl/pkg/pkgmgr" "devctl/pkg/pkgmgr/brew" "devctl/pkg/pkgmgr/scoop" @@ -29,7 +29,7 @@ func NewCmdSync(cfg *config.Config) *cobra.Command { func runSync(cfg *config.Config) error { out := ui.NewDefaultOutput() - detectingSpinner := component.NewSpinner(out) + detectingSpinner := widgets.NewSpinner(out) detectingSpinner.Start("Detecting package managers") supportedTypes := filterSupportedManagers(cfg.PackageManagers) @@ -64,7 +64,7 @@ func runSyncWithManagers(cfg *config.Config, managers map[pkgmgr.ManagerType]pkg syncCounts := make(map[pkgmgr.ManagerType]int) var warnings []string - spinner := component.NewSpinner(out) + spinner := widgets.NewSpinner(out) spinner.Start("Scanning installed packages") for managerType, mgr := range managers { @@ -88,7 +88,7 @@ func runSyncWithManagers(cfg *config.Config, managers map[pkgmgr.ManagerType]pkg cfg.Packages = config.MergePackages(cfg.Packages, allNewPackages) - saveConfigurationSpinner := component.NewSpinner(out) + saveConfigurationSpinner := widgets.NewSpinner(out) saveConfigurationSpinner.Start("Saving configuration") if err := config.SaveToFile(cfg, cfg.ConfigDir); err != nil { return fmt.Errorf("failed to save config: %w", err) diff --git a/internal/ui/output.go b/internal/ui/output.go index 292460c..410e34a 100644 --- a/internal/ui/output.go +++ b/internal/ui/output.go @@ -4,8 +4,6 @@ import ( "fmt" "io" "os" - - "devctl/pkg/pkgmgr" ) // Output defines the interface for all terminal output operations. @@ -25,56 +23,13 @@ type Output interface { // Warning prints a warning message. Warning(msg string) - // PrintDetectionResults displays package manager detection results. - PrintDetectionResults(result DetectionResult) - - // PrintInstallProgress displays installation progress messages. - PrintInstallProgress(stage, message string) - - // PrintManualGuide displays manual installation instructions. - PrintManualGuide(guide ManualGuide) - - // PrintPrerequisites displays prerequisite check results. - PrintPrerequisites(prereqs []PrerequisiteResult) - - // PrintInstallCommand displays the command that will be executed. - PrintInstallCommand(cmd string) - Print(msg string) - // Println prints a plain line without formatting. - Println(msg string) - // Printf prints a formatted message. Printf(format string, args ...any) -} - -// DetectionResult represents package manager detection results. -type DetectionResult struct { - Platform string - Managers []ManagerStatus -} -// ManagerStatus represents the status of a single package manager. -type ManagerStatus struct { - Name string - Installed bool - Path string -} - -// ManualGuide represents manual installation instructions. -type ManualGuide struct { - ManagerName string - Instructions []string - URL string - VerifyCmd string -} - -// PrerequisiteResult represents a prerequisite check result. -type PrerequisiteResult struct { - Name string - Passed bool - Message string + // Println prints a plain line without formatting. + Println(msg string) } // TerminalOutput implements Output for terminal display with colors and formatting. @@ -89,7 +44,7 @@ func NewTerminalOutput(out, errOut io.Writer) *TerminalOutput { return &TerminalOutput{ Out: out, ErrOut: errOut, - Styles: NewStyles(), + Styles: DefaultStyles, } } @@ -122,99 +77,16 @@ func (t *TerminalOutput) Warning(msg string) { fmt.Fprintf(t.Out, "%s %s\n", t.Styles.Warning.Render(IconWarning), msg) } -// PrintDetectionResults displays package manager detection results. -func (t *TerminalOutput) PrintDetectionResults(result DetectionResult) { - fmt.Fprintf(t.Out, "\n%s\n", t.Styles.Title.Render(fmt.Sprintf("Package Manager Detection (%s)", result.Platform))) - fmt.Fprintf(t.Out, "%s\n", Separator(50)) - - for _, mgr := range result.Managers { - if mgr.Installed { - fmt.Fprintf(t.Out, "%s %-10s Installed at: %s\n", - t.Styles.Success.Render(IconSuccess), - mgr.Name, - mgr.Path) - } else { - fmt.Fprintf(t.Out, "%s %-10s Not installed\n", - t.Styles.Error.Render(IconError), - mgr.Name) - } - } -} - -// PrintInstallProgress displays installation progress. -func (t *TerminalOutput) PrintInstallProgress(stage, message string) { - fmt.Fprintf(t.Out, "%s [%s] %s\n", t.Styles.Info.Render(IconInfo), stage, message) -} - -// PrintManualGuide displays manual installation instructions. -func (t *TerminalOutput) PrintManualGuide(guide ManualGuide) { - fmt.Fprintf(t.Out, "\n%s Manual Installation Guide for %s\n", - t.Styles.Title.Render("📖"), - guide.ManagerName) - fmt.Fprintf(t.Out, "%s\n", Separator(50)) - - for i, instruction := range guide.Instructions { - fmt.Fprintf(t.Out, "%d. %s\n", i+1, instruction) - } - - if guide.URL != "" { - fmt.Fprintf(t.Out, "\nMore info: %s\n", t.Styles.Info.Render(guide.URL)) - } - - if guide.VerifyCmd != "" { - fmt.Fprintf(t.Out, "Verify installation: %s\n", t.Styles.Info.Render(guide.VerifyCmd)) - } - - fmt.Fprintln(t.Out) -} - -// PrintPrerequisites displays prerequisite check results. -func (t *TerminalOutput) PrintPrerequisites(prereqs []PrerequisiteResult) { - if len(prereqs) == 0 { - return - } - fmt.Fprintln(t.Out, "Prerequisites:") - for _, prereq := range prereqs { - status := t.Styles.Success.Render(IconSuccess) - if !prereq.Passed { - status = t.Styles.Error.Render(IconError) - } - fmt.Fprintf(t.Out, " %s %s: %s\n", status, prereq.Name, prereq.Message) - } -} - -// PrintInstallCommand displays the command that will be executed. -func (t *TerminalOutput) PrintInstallCommand(cmd string) { - fmt.Fprintf(t.Out, "\nCommand to execute:\n %s\n\n", t.Styles.Info.Render(cmd)) -} - func (t *TerminalOutput) Print(msg string) { fmt.Fprint(t.Out, msg) } -// Println prints a plain line. -func (t *TerminalOutput) Println(msg string) { - fmt.Fprintln(t.Out, msg) -} - // Printf prints a formatted message. func (t *TerminalOutput) Printf(format string, args ...any) { fmt.Fprintf(t.Out, format, args...) } -// ToManagerStatus converts a map of package manager info to ManagerStatus slice. -func ToManagerStatus(managers map[pkgmgr.ManagerType]struct { - Type pkgmgr.ManagerType - Installed bool - ExecutablePath string -}) []ManagerStatus { - result := make([]ManagerStatus, 0, len(managers)) - for _, mgr := range managers { - result = append(result, ManagerStatus{ - Name: string(mgr.Type), - Installed: mgr.Installed, - Path: mgr.ExecutablePath, - }) - } - return result +// Println prints a plain line. +func (t *TerminalOutput) Println(msg string) { + fmt.Fprintln(t.Out, msg) } diff --git a/internal/ui/prompts.go b/internal/ui/prompts.go deleted file mode 100644 index c2d0b5a..0000000 --- a/internal/ui/prompts.go +++ /dev/null @@ -1,83 +0,0 @@ -package ui - -import ( - "fmt" - - "github.com/charmbracelet/huh" -) - -// ConfirmAutoInstall asks the user if they want to automatically install missing package managers. -func ConfirmAutoInstall(count int) (bool, error) { - confirmed := true - - form := huh.NewForm( - huh.NewGroup( - huh.NewConfirm(). - Title(fmt.Sprintf("Found %d uninstalled package manager(s). Install automatically?", count)). - Description("This will execute installation scripts on your system."). - Value(&confirmed), - ), - ) - - if err := form.Run(); err != nil { - return false, err - } - - return confirmed, nil -} - -// ConfirmProceed asks the user to confirm before proceeding with an installation. -func ConfirmProceed(managerName string) (bool, error) { - confirmed := true - - form := huh.NewForm( - huh.NewGroup( - huh.NewConfirm(). - Title(fmt.Sprintf("Proceed with %s installation?", managerName)). - Description("This will modify your system PATH and configuration."). - Value(&confirmed), - ), - ) - - if err := form.Run(); err != nil { - return false, err - } - - return confirmed, nil -} - -// ConfirmShowGuide asks if the user wants to see the manual installation guide. -func ConfirmShowGuide() (bool, error) { - confirmed := true - - form := huh.NewForm( - huh.NewGroup( - huh.NewConfirm(). - Title("Show manual installation guide?"). - Value(&confirmed), - ), - ) - - if err := form.Run(); err != nil { - return false, err - } - - return confirmed, nil -} - -// WaitForUserConfirmation displays a message and waits for the user to press Enter. -func WaitForUserConfirmation(message string) error { - var dummy string - - form := huh.NewForm( - huh.NewGroup( - huh.NewInput(). - Title(message). - Description("Press Enter to continue..."). - Value(&dummy). - CharLimit(0), - ), - ) - - return form.Run() -} diff --git a/internal/ui/styles.go b/internal/ui/styles.go index e44d67e..f839d41 100644 --- a/internal/ui/styles.go +++ b/internal/ui/styles.go @@ -2,6 +2,17 @@ package ui import "github.com/charmbracelet/lipgloss" +var DefaultStyles = &Styles{ + Default: lipgloss.NewStyle(), + Primary: lipgloss.NewStyle().Foreground(lipgloss.Color("#3B82F6")), + Secondary: lipgloss.NewStyle().Foreground(lipgloss.Color("#64748B")), + Success: lipgloss.NewStyle().Foreground(lipgloss.Color("#10B981")), + Info: lipgloss.NewStyle().Foreground(lipgloss.Color("#0EA5E9")), + Warning: lipgloss.NewStyle().Foreground(lipgloss.Color("#F59E0B")), + Error: lipgloss.NewStyle().Foreground(lipgloss.Color("#EF4444")), + Title: lipgloss.NewStyle().Bold(true), +} + // Styles contains all the lipgloss styles used throughout the UI. type Styles struct { Default lipgloss.Style @@ -15,20 +26,6 @@ type Styles struct { Pending lipgloss.Style } -// NewStyles creates a new Styles instance with default color scheme. -func NewStyles() *Styles { - return &Styles{ - Default: lipgloss.NewStyle(), - Primary: lipgloss.NewStyle().Foreground(lipgloss.Color("#3B82F6")), - Secondary: lipgloss.NewStyle().Foreground(lipgloss.Color("#64748B")), - Success: lipgloss.NewStyle().Foreground(lipgloss.Color("#10B981")), - Info: lipgloss.NewStyle().Foreground(lipgloss.Color("#0EA5E9")), - Warning: lipgloss.NewStyle().Foreground(lipgloss.Color("#F59E0B")), - Error: lipgloss.NewStyle().Foreground(lipgloss.Color("#EF4444")), - Title: lipgloss.NewStyle().Bold(true), - } -} - // Icon constants for consistent UI symbols. const ( IconSuccess = "✓" diff --git a/internal/ui/view/install_package.go b/internal/ui/view/install.go similarity index 99% rename from internal/ui/view/install_package.go rename to internal/ui/view/install.go index e0b010b..9ef9eeb 100644 --- a/internal/ui/view/install_package.go +++ b/internal/ui/view/install.go @@ -135,7 +135,7 @@ func (pt *ProgressTracker) StartPackage(index int) { s.Spinner = spinner.Dot model := progressModel{ - styles: ui.NewStyles(), + styles: ui.DefaultStyles, pkg: &pt.packages[index], spinner: s, } diff --git a/internal/ui/widgets/confirm_form.go b/internal/ui/widgets/confirm_form.go new file mode 100644 index 0000000..d64085c --- /dev/null +++ b/internal/ui/widgets/confirm_form.go @@ -0,0 +1,30 @@ +package widgets + +import ( + "github.com/charmbracelet/huh" +) + +type ConfirmFormConfig struct { + Title string + Desc string + DefaultValue bool +} + +func NewConfirmForm(cfg ConfirmFormConfig) (bool, error) { + confirmed := cfg.DefaultValue + + form := huh.NewForm( + huh.NewGroup( + huh.NewConfirm(). + Title(cfg.Title). + Description(cfg.Desc). + Value(&confirmed), + ), + ) + + if err := form.Run(); err != nil { + return false, err + } + + return confirmed, nil +} diff --git a/internal/ui/component/spinner.go b/internal/ui/widgets/spinner.go similarity index 97% rename from internal/ui/component/spinner.go rename to internal/ui/widgets/spinner.go index 1254e98..3a715ff 100644 --- a/internal/ui/component/spinner.go +++ b/internal/ui/widgets/spinner.go @@ -1,4 +1,4 @@ -package component +package widgets import ( "devctl/internal/ui" @@ -58,7 +58,7 @@ func (m prepSpinnerModel) View() string { func NewSpinner(output ui.Output) *PreparationSpinner { return &PreparationSpinner{ output: output, - styles: ui.NewStyles(), + styles: ui.DefaultStyles, } }