diff --git a/cmd/pm/init.go b/cmd/pm/init.go index 95e3041..9b6f5e8 100644 --- a/cmd/pm/init.go +++ b/cmd/pm/init.go @@ -2,7 +2,6 @@ package main import ( "context" - "log" "os" "github.com/LazyBachelor/LazyPM/cmd/pm/tasks" @@ -19,11 +18,7 @@ import ( ) func init() { - err := godotenv.Load(".env") - - if err != nil { - log.Println("Error loading .env file") - } + godotenv.Load(".env") models.BaseConfig = models.BaseConfig.LoadFromEnv() diff --git a/pkg/web/web.go b/pkg/web/web.go index 77b14c3..6241376 100644 --- a/pkg/web/web.go +++ b/pkg/web/web.go @@ -6,14 +6,19 @@ import ( "errors" "fmt" "net/http" + "os" + "os/signal" "strings" + "syscall" "time" + "charm.land/lipgloss/v2" "github.com/LazyBachelor/LazyPM/internal/app" "github.com/LazyBachelor/LazyPM/internal/models" "github.com/LazyBachelor/LazyPM/internal/utils/browser" "github.com/LazyBachelor/LazyPM/pkg/web/handler" "github.com/LazyBachelor/LazyPM/pkg/web/server" + tea "github.com/charmbracelet/bubbletea" ) type Config = models.Config @@ -32,12 +37,50 @@ func New() *Web { //go:embed assets/* var assets embed.FS +type tuiModel struct { + width int + height int + address string + quitChan chan struct{} +} + +func (m tuiModel) Init() tea.Cmd { + return nil +} + +func (m tuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.width = msg.Width + m.height = msg.Height + return m, nil + case tea.KeyMsg: + switch msg.String() { + case "q", "esc", "ctrl+c": + select { + case <-m.quitChan: + default: + close(m.quitChan) + } + return m, tea.Quit + } + } + + return m, nil +} + +func (m tuiModel) View() string { + return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, fmt.Sprintf( + "Web server running at %s\n\nPress q, esc, or Ctrl+C to stop the task and server.\n", + m.address, + )) +} + func (w *Web) Run(ctx context.Context, config Config) error { app, cleanup, err := app.New(ctx, config) if err != nil { return err } - defer cleanup() httpServer := server.NewServer(server.Server{ @@ -46,21 +89,39 @@ func (w *Web) Run(ctx context.Context, config Config) error { App: app, }) - fmt.Printf("Starting web server on %s...\n", config.WebAddress) - address := config.WebAddress if !strings.Contains(address, "http") { address = "http://localhost" + address } - err = browser.Open(address) - if err != nil { - return fmt.Errorf("failed to open browser: %w", err) - } - serverErr := make(chan error, 1) + uiQuitChan := make(chan struct{}) + + screen := tea.NewProgram( + tuiModel{ + address: address, + quitChan: uiQuitChan, + }, + tea.WithAltScreen(), + ) + + screenDone := make(chan struct{}) + go func() { - if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + defer close(screenDone) + + if _, err := screen.Run(); err != nil { + fmt.Printf("failed to start terminal UI: %s\n", err) + } + }() + + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + defer signal.Stop(sigChan) + + go func() { + if err := httpServer.ListenAndServe(); err != nil && + !errors.Is(err, http.ErrServerClosed) { serverErr <- err } }() @@ -77,17 +138,36 @@ func (w *Web) Run(ctx context.Context, config Config) error { handler.SetSubmitChan(w.submitChan) } + if err := browser.Open(address); err != nil { + fmt.Printf("failed to open browser: %s\n", err) + } + + var shutdownMsg string + select { case <-w.quitChan: - fmt.Println("Task completed! Shutting down server...") - shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - return httpServer.Shutdown(shutdownCtx) - case err := <-serverErr: - return err + shutdownMsg = "Task completed! Ending task and server..." + case <-uiQuitChan: + shutdownMsg = "Quit key pressed. Ending task and server..." + case sig := <-sigChan: + shutdownMsg = fmt.Sprintf("Received signal %s. Ending task and server...", sig) case <-ctx.Done(): - return httpServer.Shutdown(context.Background()) + shutdownMsg = "Context cancelled. Ending task and server..." + case err := <-serverErr: + screen.Quit() + <-screenDone + return err } + + screen.Quit() + <-screenDone + + fmt.Println(shutdownMsg) + + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + return httpServer.Shutdown(shutdownCtx) } func (w *Web) SetChannels(feedbackChan chan ValidationFeedback, quitChan chan bool) {