diff --git a/pkg/views/workspace/create/configuration.go b/pkg/views/workspace/create/configuration.go index 2a09af0304..6fb73170db 100644 --- a/pkg/views/workspace/create/configuration.go +++ b/pkg/views/workspace/create/configuration.go @@ -23,7 +23,7 @@ const ( DEVCONTAINER_FILEPATH = ".devcontainer/devcontainer.json" ) -var configurationHelpLine = lipgloss.NewStyle().Foreground(views.Gray).Render("enter: next f10: advanced configuration") +var helpStyle = lipgloss.NewStyle().Foreground(views.Gray) type ProjectConfigurationData struct { BuildChoice string diff --git a/pkg/views/workspace/create/summary.go b/pkg/views/workspace/create/summary.go index d265318117..ca879a8381 100644 --- a/pkg/views/workspace/create/summary.go +++ b/pkg/views/workspace/create/summary.go @@ -7,8 +7,13 @@ import ( "errors" "fmt" "log" + "math" + "os" + "slices" + "sort" "strings" + "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/huh" "github.com/charmbracelet/lipgloss" @@ -16,6 +21,7 @@ import ( "github.com/daytonaio/daytona/pkg/apiclient" "github.com/daytonaio/daytona/pkg/views" views_util "github.com/daytonaio/daytona/pkg/views/util" + "golang.org/x/term" ) type ProjectDetail string @@ -35,11 +41,13 @@ type SummaryModel struct { styles *Styles form *huh.Form width int + height int quitting bool name string projectList []apiclient.CreateProjectDTO defaults *views_util.ProjectConfigDefaults nameLabel string + viewport viewport.Model } type SubmissionFormConfig struct { @@ -143,18 +151,57 @@ func renderProjectDetails(project apiclient.CreateProjectDTO, buildChoice views_ output += "\n" } - var envVars string - for key, val := range project.EnvVars { - envVars += fmt.Sprintf("%s=%s; ", key, val) + keys := make([]string, 0, len(project.EnvVars)) + for key := range project.EnvVars { + keys = append(keys, key) } - output += projectDetailOutput(EnvVars, strings.TrimSuffix(envVars, "; ")) + sort.Strings(keys) + + var envVarsBuilder strings.Builder + for _, key := range keys { + envVarsBuilder.WriteString(key + "=" + project.EnvVars[key] + "; ") + } + + envVars := envVarsBuilder.String() + if len(envVars) > 2 { + envVars = envVars[:len(envVars)-2] + } + + output += projectDetailOutput("EnvVars", envVars) } return output } func projectDetailOutput(projectDetailKey ProjectDetail, projectDetailValue string) string { - return fmt.Sprintf("\t%s%-*s%s", lipgloss.NewStyle().Foreground(views.Green).Render(string(projectDetailKey)), DEFAULT_PADDING-len(string(projectDetailKey)), EMPTY_STRING, projectDetailValue) + keyStyle := lipgloss.NewStyle().Foreground(views.Green).Render(string(projectDetailKey)) + + if projectDetailKey == EnvVars { + projectDetailValue = strings.ReplaceAll(projectDetailValue, "; ", "\n\t\t") + return fmt.Sprintf("\t%s\n\t\t%s", keyStyle, projectDetailValue) + } + return fmt.Sprintf("\t%s%-*s%s", + keyStyle, + DEFAULT_PADDING-len(string(projectDetailKey)), + EMPTY_STRING, + projectDetailValue) +} + +func calculateViewportSize(content string) (width, height int) { + lines := strings.Split(content, "\n") + longestLine := slices.MaxFunc(lines, func(a, b string) int { + return len(a) - len(b) + }) + width = len(longestLine) + + _, maxHeight, err := term.GetSize(int(os.Stdout.Fd())) + if err != nil { + maxHeight = 25 + } + + height = int(math.Min(float64(len(lines)), float64(maxHeight))) + + return width, height } func NewSummaryModel(config SubmissionFormConfig) SummaryModel { @@ -192,6 +239,13 @@ func NewSummaryModel(config SubmissionFormConfig) SummaryModel { ), ).WithShowHelp(false).WithTheme(views.GetCustomTheme()) + content, _ := RenderSummary(m.name, m.projectList, m.defaults, m.nameLabel) + + // Dynamically calculate viewport size + m.width, m.height = calculateViewportSize(content) + m.viewport = viewport.New(m.width, m.height) + m.viewport.SetContent(content) + return m } @@ -212,7 +266,15 @@ func (m SummaryModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.form.State = huh.StateCompleted configureCheck = true return m, tea.Quit + case "up": + m.viewport.LineUp(1) // Scroll up + case "down": + m.viewport.LineDown(1) // Scroll down } + case tea.WindowSizeMsg: + m.viewport.Height = max(1, min(m.height, msg.Height-15)) + m.viewport.Width = max(20, min(maxWidth, min(m.width, msg.Width-15))) + } var cmds []tea.Cmd @@ -238,15 +300,37 @@ func (m SummaryModel) View() string { return "" } - view := m.form.WithHeight(5).View() + "\n" + configurationHelpLine + helpLine := helpStyle.Render("enter: next • f10: advanced configuration") + var content string - if len(m.projectList) > 1 || len(m.projectList) == 1 && ProjectsConfigurationChanged { - summary, err := RenderSummary(m.name, m.projectList, m.defaults, m.nameLabel) - if err != nil { - log.Fatal(err) - } - view = views.GetBorderedMessage(summary) + "\n" + view + if len(m.projectList) > 1 || ProjectsConfigurationChanged { + content = renderSummaryView(m) + } else { + content = m.form.WithHeight(5).View() + } + + return content + "\n" + helpLine +} + +func renderSummaryView(m SummaryModel) string { + summary, err := RenderSummary(m.name, m.projectList, m.defaults, m.nameLabel) + if err != nil { + log.Fatal(err) } + m.viewport.SetContent(summary) + + return lipgloss.JoinVertical(lipgloss.Top, renderBody(m), renderFooter(m)) + m.form.WithHeight(5).View() +} + +func renderBody(m SummaryModel) string { + return m.viewport.Style. + Margin(1, 0, 0). + Padding(1, 2). + BorderForeground(views.LightGray). + Border(lipgloss.RoundedBorder()). + Render(m.viewport.View()) +} - return view +func renderFooter(m SummaryModel) string { + return helpStyle.Align(lipgloss.Right).Width(m.viewport.Width + 4).Render("↑ up • ↓ down") }