From a26d1672d97bc921d30b0e0a0718818f87eded0d Mon Sep 17 00:00:00 2001 From: bagas Date: Tue, 27 Jan 2026 13:43:18 +0700 Subject: [PATCH] refactor(interaction): reduce cognitive complexity and centralize color constants --- session/interaction/commands.go | 37 +++-- session/interaction/dashboard.go | 250 +++++++++++++++------------- session/interaction/model.go | 19 +++ session/interaction/slug.go | 270 ++++++++++++++++++------------- 4 files changed, 335 insertions(+), 241 deletions(-) diff --git a/session/interaction/commands.go b/session/interaction/commands.go index e884aeb..5e24368 100644 --- a/session/interaction/commands.go +++ b/session/interaction/commands.go @@ -10,34 +10,37 @@ import ( "github.com/charmbracelet/lipgloss" ) +func (m *model) handleCommandSelection(item commandItem) (tea.Model, tea.Cmd) { + switch item.name { + case "slug": + m.showingCommands = false + m.editingSlug = true + m.slugInput.SetValue(m.interaction.slug.String()) + m.slugInput.Focus() + return m, tea.Batch(tea.ClearScreen, textinput.Blink) + case "tunnel-type": + m.showingCommands = false + m.showingComingSoon = true + return m, tea.Batch(tickCmd(5*time.Second), tea.ClearScreen, textinput.Blink) + default: + m.showingCommands = false + return m, nil + } +} + func (m *model) commandsUpdate(msg tea.KeyMsg) (tea.Model, tea.Cmd) { var cmd tea.Cmd switch { - case key.Matches(msg, m.keymap.quit): + case key.Matches(msg, m.keymap.quit), msg.String() == "esc": m.showingCommands = false return m, tea.Batch(tea.ClearScreen, textinput.Blink) case msg.String() == "enter": selectedItem := m.commandList.SelectedItem() if selectedItem != nil { item := selectedItem.(commandItem) - if item.name == "slug" { - m.showingCommands = false - m.editingSlug = true - m.slugInput.SetValue(m.interaction.slug.String()) - m.slugInput.Focus() - return m, tea.Batch(tea.ClearScreen, textinput.Blink) - } else if item.name == "tunnel-type" { - m.showingCommands = false - m.showingComingSoon = true - return m, tea.Batch(tickCmd(5*time.Second), tea.ClearScreen, textinput.Blink) - } - m.showingCommands = false - return m, nil + return m.handleCommandSelection(item) } - case msg.String() == "esc": - m.showingCommands = false - return m, tea.Batch(tea.ClearScreen, textinput.Blink) } m.commandList, cmd = m.commandList.Update(msg) return m, cmd diff --git a/session/interaction/dashboard.go b/session/interaction/dashboard.go index a24ab7c..cf10ddb 100644 --- a/session/interaction/dashboard.go +++ b/session/interaction/dashboard.go @@ -23,164 +23,194 @@ func (m *model) dashboardUpdate(msg tea.KeyMsg) (tea.Model, tea.Cmd) { } func (m *model) dashboardView() string { - titleStyle := lipgloss.NewStyle(). - Bold(true). - Foreground(lipgloss.Color("#7D56F4")). - PaddingTop(1) - - subtitleStyle := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#888888")). - Italic(true) - - urlStyle := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#7D56F4")). - Underline(true). - Italic(true) - - urlBoxStyle := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#04B575")). - Bold(true). - Italic(true) - - keyHintStyle := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#7D56F4")). - Bold(true) + isCompact := shouldUseCompactLayout(m.width, BreakpointLarge) var b strings.Builder + b.WriteString(m.renderHeader(isCompact)) + b.WriteString(m.renderUserInfo(isCompact)) + b.WriteString(m.renderQuickActions(isCompact)) + b.WriteString(m.renderFooter(isCompact)) - isCompact := shouldUseCompactLayout(m.width, 85) + return b.String() +} - var asciiArtMargin int - if isCompact { - asciiArtMargin = 0 - } else { - asciiArtMargin = 1 - } +func (m *model) renderHeader(isCompact bool) string { + var b strings.Builder + asciiArtMargin := getMarginValue(isCompact, 0, 1) asciiArtStyle := lipgloss.NewStyle(). Bold(true). - Foreground(lipgloss.Color("#7D56F4")). + Foreground(lipgloss.Color(ColorPrimary)). MarginBottom(asciiArtMargin) - var asciiArt string - if shouldUseCompactLayout(m.width, 50) { - asciiArt = "TUNNEL PLS" - } else if isCompact { - asciiArt = ` + b.WriteString(asciiArtStyle.Render(m.getASCIIArt())) + b.WriteString("\n") + + if !shouldUseCompactLayout(m.width, BreakpointSmall) { + b.WriteString(m.renderSubtitle()) + } else { + b.WriteString("\n") + } + + return b.String() +} + +func (m *model) getASCIIArt() string { + if shouldUseCompactLayout(m.width, BreakpointTiny) { + return "TUNNEL PLS" + } + + if shouldUseCompactLayout(m.width, BreakpointLarge) { + return ` ▀█▀ █ █ █▄ █ █▄ █ ██▀ █ ▄▀▀ █ ▄▀▀ █ ▀▄█ █ ▀█ █ ▀█ █▄▄ █▄▄ ▄█▀ █▄▄ ▄█▀` - } else { - asciiArt = ` + } + + return ` ████████╗██╗ ██╗███╗ ██╗███╗ ██╗███████╗██╗ ██████╗ ██╗ ███████╗ ╚══██╔══╝██║ ██║████╗ ██║████╗ ██║██╔════╝██║ ██╔══██╗██║ ██╔════╝ ██║ ██║ ██║██╔██╗ ██║██╔██╗ ██║█████╗ ██║ ██████╔╝██║ ███████╗ ██║ ██║ ██║██║╚██╗██║██║╚██╗██║██╔══╝ ██║ ██╔═══╝ ██║ ╚════██║ ██║ ╚██████╔╝██║ ╚████║██║ ╚████║███████╗███████╗ ██║ ███████╗███████║ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═══╝╚══════╝╚══════╝ ╚═╝ ╚══════╝╚══════╝` - } +} - b.WriteString(asciiArtStyle.Render(asciiArt)) - b.WriteString("\n") +func (m *model) renderSubtitle() string { + subtitleStyle := lipgloss.NewStyle(). + Foreground(lipgloss.Color(ColorGray)). + Italic(true) - if !shouldUseCompactLayout(m.width, 60) { - b.WriteString(subtitleStyle.Render("Secure tunnel service by Bagas • ")) - b.WriteString(urlStyle.Render("https://fossy.my.id")) - b.WriteString("\n\n") - } else { - b.WriteString("\n") - } + urlStyle := lipgloss.NewStyle(). + Foreground(lipgloss.Color(ColorPrimary)). + Underline(true). + Italic(true) + return subtitleStyle.Render("Secure tunnel service by Bagas • ") + + urlStyle.Render("https://fossy.my.id") + "\n\n" +} + +func (m *model) renderUserInfo(isCompact bool) string { boxMaxWidth := getResponsiveWidth(m.width, 10, 40, 80) - var boxPadding int - var boxMargin int - if isCompact { - boxPadding = 1 - boxMargin = 1 - } else { - boxPadding = 2 - boxMargin = 2 - } + boxPadding := getMarginValue(isCompact, 1, 2) + boxMargin := getMarginValue(isCompact, 1, 2) responsiveInfoBox := lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). - BorderForeground(lipgloss.Color("#7D56F4")). + BorderForeground(lipgloss.Color(ColorPrimary)). Padding(1, boxPadding). MarginTop(boxMargin). MarginBottom(boxMargin). Width(boxMaxWidth) - authenticatedUser := m.interaction.user + infoContent := m.getUserInfoContent(isCompact) + return responsiveInfoBox.Render(infoContent) + "\n" +} +func (m *model) getUserInfoContent(isCompact bool) string { userInfoStyle := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#FAFAFA")). + Foreground(lipgloss.Color(ColorWhite)). Bold(true) sectionHeaderStyle := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#888888")). + Foreground(lipgloss.Color(ColorGray)). Bold(true) addressStyle := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#FAFAFA")) + Foreground(lipgloss.Color(ColorWhite)) - var infoContent string - if shouldUseCompactLayout(m.width, 70) { - infoContent = fmt.Sprintf("👤 %s\n\n%s\n%s", + urlBoxStyle := lipgloss.NewStyle(). + Foreground(lipgloss.Color(ColorSecondary)). + Bold(true). + Italic(true) + + authenticatedUser := m.interaction.user + tunnelURL := urlBoxStyle.Render(m.getTunnelURL()) + + if isCompact { + return fmt.Sprintf("👤 %s\n\n%s\n%s", userInfoStyle.Render(authenticatedUser), sectionHeaderStyle.Render("🌐 FORWARDING ADDRESS:"), - addressStyle.Render(fmt.Sprintf(" %s", urlBoxStyle.Render(m.getTunnelURL())))) - } else { - infoContent = fmt.Sprintf("👤 Authenticated as: %s\n\n%s\n %s", - userInfoStyle.Render(authenticatedUser), - sectionHeaderStyle.Render("🌐 FORWARDING ADDRESS:"), - addressStyle.Render(urlBoxStyle.Render(m.getTunnelURL()))) + addressStyle.Render(fmt.Sprintf(" %s", tunnelURL))) } - b.WriteString(responsiveInfoBox.Render(infoContent)) + return fmt.Sprintf("👤 Authenticated as: %s\n\n%s\n %s", + userInfoStyle.Render(authenticatedUser), + sectionHeaderStyle.Render("🌐 FORWARDING ADDRESS:"), + addressStyle.Render(tunnelURL)) +} + +func (m *model) renderQuickActions(isCompact bool) string { + var b strings.Builder + + titleStyle := lipgloss.NewStyle(). + Bold(true). + Foreground(lipgloss.Color(ColorPrimary)). + PaddingTop(1) + + b.WriteString(titleStyle.Render(m.getQuickActionsTitle())) b.WriteString("\n") - var quickActionsTitle string - if shouldUseCompactLayout(m.width, 50) { - quickActionsTitle = "Actions" - } else if isCompact { - quickActionsTitle = "Quick Actions" - } else { - quickActionsTitle = "✨ Quick Actions" - } - b.WriteString(titleStyle.Render(quickActionsTitle)) - b.WriteString("\n") - - var featureMargin int - if isCompact { - featureMargin = 1 - } else { - featureMargin = 2 - } - - compactFeatureStyle := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#FAFAFA")). + featureMargin := getMarginValue(isCompact, 1, 2) + featureStyle := lipgloss.NewStyle(). + Foreground(lipgloss.Color(ColorWhite)). MarginLeft(featureMargin) - var commandsText string - var quitText string - if shouldUseCompactLayout(m.width, 60) { - commandsText = fmt.Sprintf(" %s Commands", keyHintStyle.Render("[C]")) - quitText = fmt.Sprintf(" %s Quit", keyHintStyle.Render("[Q]")) - } else { - commandsText = fmt.Sprintf(" %s Open commands menu", keyHintStyle.Render("[C]")) - quitText = fmt.Sprintf(" %s Quit application", keyHintStyle.Render("[Q]")) - } + keyHintStyle := lipgloss.NewStyle(). + Foreground(lipgloss.Color(ColorPrimary)). + Bold(true) - b.WriteString(compactFeatureStyle.Render(commandsText)) + commands := m.getActionCommands(keyHintStyle) + b.WriteString(featureStyle.Render(commands.commandsText)) b.WriteString("\n") - b.WriteString(compactFeatureStyle.Render(quitText)) - - if !shouldUseCompactLayout(m.width, 70) { - b.WriteString("\n\n") - footerStyle := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#666666")). - Italic(true) - b.WriteString(footerStyle.Render("Press 'C' to customize your tunnel settings")) - } + b.WriteString(featureStyle.Render(commands.quitText)) return b.String() } + +func (m *model) getQuickActionsTitle() string { + if shouldUseCompactLayout(m.width, BreakpointTiny) { + return "Actions" + } + if shouldUseCompactLayout(m.width, BreakpointLarge) { + return "Quick Actions" + } + return "✨ Quick Actions" +} + +type actionCommands struct { + commandsText string + quitText string +} + +func (m *model) getActionCommands(keyHintStyle lipgloss.Style) actionCommands { + if shouldUseCompactLayout(m.width, BreakpointSmall) { + return actionCommands{ + commandsText: fmt.Sprintf(" %s Commands", keyHintStyle.Render("[C]")), + quitText: fmt.Sprintf(" %s Quit", keyHintStyle.Render("[Q]")), + } + } + + return actionCommands{ + commandsText: fmt.Sprintf(" %s Open commands menu", keyHintStyle.Render("[C]")), + quitText: fmt.Sprintf(" %s Quit application", keyHintStyle.Render("[Q]")), + } +} + +func (m *model) renderFooter(isCompact bool) string { + if isCompact { + return "" + } + + footerStyle := lipgloss.NewStyle(). + Foreground(lipgloss.Color(ColorDarkGray)). + Italic(true) + + return "\n\n" + footerStyle.Render("Press 'C' to customize your tunnel settings") +} + +func getMarginValue(isCompact bool, compactValue, normalValue int) int { + if isCompact { + return compactValue + } + return normalValue +} diff --git a/session/interaction/model.go b/session/interaction/model.go index 3002d16..c0d1672 100644 --- a/session/interaction/model.go +++ b/session/interaction/model.go @@ -42,6 +42,25 @@ type model struct { height int } +const ( + ColorPrimary = "#7D56F4" + ColorSecondary = "#04B575" + ColorGray = "#888888" + ColorDarkGray = "#666666" + ColorWhite = "#FAFAFA" + ColorError = "#FF0000" + ColorErrorBg = "#3D0000" + ColorWarning = "#FFA500" + ColorWarningBg = "#3D2000" +) + +const ( + BreakpointTiny = 50 + BreakpointSmall = 60 + BreakpointMedium = 70 + BreakpointLarge = 85 +) + func (m *model) getTunnelURL() string { if m.tunnelType == types.TunnelTypeHTTP { return buildURL(m.protocol, m.interaction.slug.String(), m.domain) diff --git a/session/interaction/slug.go b/session/interaction/slug.go index 647cd31..ff57cb1 100644 --- a/session/interaction/slug.go +++ b/session/interaction/slug.go @@ -21,7 +21,7 @@ func (m *model) slugUpdate(msg tea.KeyMsg) (tea.Model, tea.Cmd) { } switch msg.String() { - case "esc": + case "esc", "ctrl+c": m.editingSlug = false m.slugError = "" return m, tea.Batch(tea.ClearScreen, textinput.Blink) @@ -40,10 +40,6 @@ func (m *model) slugUpdate(msg tea.KeyMsg) (tea.Model, tea.Cmd) { m.editingSlug = false m.slugError = "" return m, tea.Batch(tea.ClearScreen, textinput.Blink) - case "ctrl+c": - m.editingSlug = false - m.slugError = "" - return m, tea.Batch(tea.ClearScreen, textinput.Blink) default: if key.Matches(msg, m.keymap.random) { newSubdomain, err := m.randomizer.String(20) @@ -51,8 +47,6 @@ func (m *model) slugUpdate(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return m, cmd } m.slugInput.SetValue(newSubdomain) - m.slugError = "" - m.slugInput, cmd = m.slugInput.Update(msg) } m.slugError = "" m.slugInput, cmd = m.slugInput.Update(msg) @@ -61,163 +55,211 @@ func (m *model) slugUpdate(msg tea.KeyMsg) (tea.Model, tea.Cmd) { } func (m *model) slugView() string { - isCompact := shouldUseCompactLayout(m.width, 70) - isVeryCompact := shouldUseCompactLayout(m.width, 50) + isCompact := shouldUseCompactLayout(m.width, BreakpointMedium) + isVeryCompact := shouldUseCompactLayout(m.width, BreakpointTiny) - var boxPadding int - var boxMargin int - if isVeryCompact { - boxPadding = 1 - boxMargin = 1 - } else if isCompact { - boxPadding = 1 - boxMargin = 1 - } else { - boxPadding = 2 - boxMargin = 2 + var b strings.Builder + b.WriteString(m.renderSlugTitle(isVeryCompact)) + + if m.tunnelType != types.TunnelTypeHTTP { + b.WriteString(m.renderTCPWarning(isVeryCompact, isCompact)) + return b.String() } + b.WriteString(m.renderSlugRules(isVeryCompact, isCompact)) + b.WriteString(m.renderSlugInstruction(isVeryCompact)) + b.WriteString(m.renderSlugInput(isVeryCompact, isCompact)) + b.WriteString(m.renderSlugPreview(isVeryCompact)) + b.WriteString(m.renderSlugHelp(isVeryCompact)) + + return b.String() +} + +func (m *model) renderSlugTitle(isVeryCompact bool) string { titleStyle := lipgloss.NewStyle(). Bold(true). - Foreground(lipgloss.Color("#7D56F4")). + Foreground(lipgloss.Color(ColorPrimary)). PaddingTop(1). PaddingBottom(1) - instructionStyle := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#FAFAFA")). - MarginTop(1) + title := "🔧 Edit Subdomain" + if isVeryCompact { + title = "Edit Subdomain" + } - inputBoxStyle := lipgloss.NewStyle(). + return titleStyle.Render(title) + "\n\n" +} + +func (m *model) renderTCPWarning(isVeryCompact, isCompact bool) string { + boxPadding := getPaddingValue(isVeryCompact, isCompact) + boxMargin := getMarginValue(isCompact, 1, 2) + warningBoxWidth := getResponsiveWidth(m.width, 10, 30, 60) + + warningBoxStyle := lipgloss.NewStyle(). + Foreground(lipgloss.Color(ColorWarning)). + Background(lipgloss.Color(ColorWarningBg)). + Bold(true). Border(lipgloss.RoundedBorder()). - BorderForeground(lipgloss.Color("#7D56F4")). + BorderForeground(lipgloss.Color(ColorWarning)). Padding(1, boxPadding). MarginTop(boxMargin). - MarginBottom(boxMargin) + MarginBottom(boxMargin). + Width(warningBoxWidth) helpStyle := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#666666")). + Foreground(lipgloss.Color(ColorDarkGray)). Italic(true). MarginTop(1) - errorBoxStyle := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#FF0000")). - Background(lipgloss.Color("#3D0000")). - Bold(true). - Border(lipgloss.RoundedBorder()). - BorderForeground(lipgloss.Color("#FF0000")). - Padding(0, boxPadding). - MarginTop(1). - MarginBottom(1) + warningText := m.getTCPWarningText(isVeryCompact) + helpText := m.getTCPHelpText(isVeryCompact) + var b strings.Builder + b.WriteString(warningBoxStyle.Render(warningText)) + b.WriteString("\n\n") + b.WriteString(helpStyle.Render(helpText)) + + return b.String() +} + +func (m *model) getTCPWarningText(isVeryCompact bool) string { + if isVeryCompact { + return "⚠️ TCP tunnels don't support custom subdomains." + } + return "⚠️ TCP tunnels cannot have custom subdomains. Only HTTP/HTTPS tunnels support subdomain customization." +} + +func (m *model) getTCPHelpText(isVeryCompact bool) string { + if isVeryCompact { + return "Press any key to go back" + } + return "Press Enter or Esc to go back" +} + +func (m *model) renderSlugRules(isVeryCompact, isCompact bool) string { + boxPadding := getPaddingValue(isVeryCompact, isCompact) rulesBoxWidth := getResponsiveWidth(m.width, 10, 30, 60) + rulesBoxStyle := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#FAFAFA")). + Foreground(lipgloss.Color(ColorWhite)). Border(lipgloss.RoundedBorder()). - BorderForeground(lipgloss.Color("#7D56F4")). + BorderForeground(lipgloss.Color(ColorPrimary)). Padding(0, boxPadding). MarginTop(1). MarginBottom(1). Width(rulesBoxWidth) - var b strings.Builder - var title string + rulesContent := m.getRulesContent(isVeryCompact, isCompact) + return rulesBoxStyle.Render(rulesContent) + "\n" +} + +func (m *model) getRulesContent(isVeryCompact, isCompact bool) string { if isVeryCompact { - title = "Edit Subdomain" - } else { - title = "🔧 Edit Subdomain" - } - b.WriteString(titleStyle.Render(title)) - b.WriteString("\n\n") - - if m.tunnelType != types.TunnelTypeHTTP { - warningBoxWidth := getResponsiveWidth(m.width, 10, 30, 60) - warningBoxStyle := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#FFA500")). - Background(lipgloss.Color("#3D2000")). - Bold(true). - Border(lipgloss.RoundedBorder()). - BorderForeground(lipgloss.Color("#FFA500")). - Padding(1, boxPadding). - MarginTop(boxMargin). - MarginBottom(boxMargin). - Width(warningBoxWidth) - - var warningText string - if isVeryCompact { - warningText = "⚠️ TCP tunnels don't support custom subdomains." - } else { - warningText = "⚠️ TCP tunnels cannot have custom subdomains. Only HTTP/HTTPS tunnels support subdomain customization." - } - b.WriteString(warningBoxStyle.Render(warningText)) - b.WriteString("\n\n") - - var helpText string - if isVeryCompact { - helpText = "Press any key to go back" - } else { - helpText = "Press Enter or Esc to go back" - } - b.WriteString(helpStyle.Render(helpText)) - return b.String() + return "Rules:\n3-20 chars\na-z, 0-9, -\nNo leading/trailing -" } - var rulesContent string - if isVeryCompact { - rulesContent = "Rules:\n3-20 chars\na-z, 0-9, -\nNo leading/trailing -" - } else if isCompact { - rulesContent = "📋 Rules:\n • 3-20 chars\n • a-z, 0-9, -\n • No leading/trailing -" - } else { - rulesContent = "📋 Rules: \n\t• 3-20 chars \n\t• a-z, 0-9, - \n\t• No leading/trailing -" + if isCompact { + return "📋 Rules:\n • 3-20 chars\n • a-z, 0-9, -\n • No leading/trailing -" } - b.WriteString(rulesBoxStyle.Render(rulesContent)) - b.WriteString("\n") - var instruction string + return "📋 Rules: \n\t• 3-20 chars \n\t• a-z, 0-9, - \n\t• No leading/trailing -" +} + +func (m *model) renderSlugInstruction(isVeryCompact bool) string { + instructionStyle := lipgloss.NewStyle(). + Foreground(lipgloss.Color(ColorWhite)). + MarginTop(1) + + instruction := "Enter your custom subdomain:" if isVeryCompact { instruction = "Custom subdomain:" - } else { - instruction = "Enter your custom subdomain:" } - b.WriteString(instructionStyle.Render(instruction)) - b.WriteString("\n") + + return instructionStyle.Render(instruction) + "\n" +} + +func (m *model) renderSlugInput(isVeryCompact, isCompact bool) string { + boxPadding := getPaddingValue(isVeryCompact, isCompact) + boxMargin := getMarginValue(isCompact, 1, 2) if m.slugError != "" { - errorInputBoxStyle := lipgloss.NewStyle(). - Border(lipgloss.RoundedBorder()). - BorderForeground(lipgloss.Color("#FF0000")). - Padding(1, boxPadding). - MarginTop(boxMargin). - MarginBottom(1) - b.WriteString(errorInputBoxStyle.Render(m.slugInput.View())) - b.WriteString("\n") - b.WriteString(errorBoxStyle.Render("❌ " + m.slugError)) - b.WriteString("\n") - } else { - b.WriteString(inputBoxStyle.Render(m.slugInput.View())) - b.WriteString("\n") + return m.renderErrorInput(boxPadding, boxMargin) } + return m.renderNormalInput(boxPadding, boxMargin) +} + +func (m *model) renderErrorInput(boxPadding, boxMargin int) string { + errorInputBoxStyle := lipgloss.NewStyle(). + Border(lipgloss.RoundedBorder()). + BorderForeground(lipgloss.Color(ColorError)). + Padding(1, boxPadding). + MarginTop(boxMargin). + MarginBottom(1) + + errorBoxStyle := lipgloss.NewStyle(). + Foreground(lipgloss.Color(ColorError)). + Background(lipgloss.Color(ColorErrorBg)). + Bold(true). + Border(lipgloss.RoundedBorder()). + BorderForeground(lipgloss.Color(ColorError)). + Padding(0, boxPadding). + MarginTop(1). + MarginBottom(1) + + var b strings.Builder + b.WriteString(errorInputBoxStyle.Render(m.slugInput.View())) + b.WriteString("\n") + b.WriteString(errorBoxStyle.Render("❌ " + m.slugError)) + b.WriteString("\n") + + return b.String() +} + +func (m *model) renderNormalInput(boxPadding, boxMargin int) string { + inputBoxStyle := lipgloss.NewStyle(). + Border(lipgloss.RoundedBorder()). + BorderForeground(lipgloss.Color(ColorPrimary)). + Padding(1, boxPadding). + MarginTop(boxMargin). + MarginBottom(boxMargin) + + return inputBoxStyle.Render(m.slugInput.View()) + "\n" +} + +func (m *model) renderSlugPreview(isVeryCompact bool) string { previewURL := buildURL(m.protocol, m.slugInput.Value(), m.domain) previewWidth := getResponsiveWidth(m.width, 10, 30, 80) - if len(previewURL) > previewWidth-10 { + if isVeryCompact { previewURL = truncateString(previewURL, previewWidth-10) } previewStyle := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#04B575")). + Foreground(lipgloss.Color(ColorSecondary)). Italic(true). Width(previewWidth) - b.WriteString(previewStyle.Render(fmt.Sprintf("Preview: %s", previewURL))) - b.WriteString("\n") - var helpText string + return previewStyle.Render(fmt.Sprintf("Preview: %s", previewURL)) + "\n" +} + +func (m *model) renderSlugHelp(isVeryCompact bool) string { + helpStyle := lipgloss.NewStyle(). + Foreground(lipgloss.Color(ColorDarkGray)). + Italic(true). + MarginTop(1) + + helpText := "Press Enter to save • CTRL+R for random • Esc to cancel" if isVeryCompact { helpText = "Enter: save • CTRL+R: random • Esc: cancel" - } else { - helpText = "Press Enter to save • CTRL+R for random • Esc to cancel" } - b.WriteString(helpStyle.Render(helpText)) - return b.String() + return helpStyle.Render(helpText) +} + +func getPaddingValue(isVeryCompact, isCompact bool) int { + if isVeryCompact || isCompact { + return 1 + } + return 2 }