revert-54069ad305 #11
@@ -10,34 +10,37 @@ import (
|
|||||||
"github.com/charmbracelet/lipgloss"
|
"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) {
|
func (m *model) commandsUpdate(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||||
var cmd tea.Cmd
|
var cmd tea.Cmd
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case key.Matches(msg, m.keymap.quit):
|
case key.Matches(msg, m.keymap.quit), msg.String() == "esc":
|
||||||
m.showingCommands = false
|
m.showingCommands = false
|
||||||
return m, tea.Batch(tea.ClearScreen, textinput.Blink)
|
return m, tea.Batch(tea.ClearScreen, textinput.Blink)
|
||||||
case msg.String() == "enter":
|
case msg.String() == "enter":
|
||||||
selectedItem := m.commandList.SelectedItem()
|
selectedItem := m.commandList.SelectedItem()
|
||||||
if selectedItem != nil {
|
if selectedItem != nil {
|
||||||
item := selectedItem.(commandItem)
|
item := selectedItem.(commandItem)
|
||||||
if item.name == "slug" {
|
return m.handleCommandSelection(item)
|
||||||
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
|
|
||||||
}
|
|
||||||
case msg.String() == "esc":
|
|
||||||
m.showingCommands = false
|
|
||||||
return m, tea.Batch(tea.ClearScreen, textinput.Blink)
|
|
||||||
}
|
}
|
||||||
m.commandList, cmd = m.commandList.Update(msg)
|
m.commandList, cmd = m.commandList.Update(msg)
|
||||||
return m, cmd
|
return m, cmd
|
||||||
|
|||||||
+141
-111
@@ -23,164 +23,194 @@ func (m *model) dashboardUpdate(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *model) dashboardView() string {
|
func (m *model) dashboardView() string {
|
||||||
titleStyle := lipgloss.NewStyle().
|
isCompact := shouldUseCompactLayout(m.width, BreakpointLarge)
|
||||||
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)
|
|
||||||
|
|
||||||
var b strings.Builder
|
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
|
func (m *model) renderHeader(isCompact bool) string {
|
||||||
if isCompact {
|
var b strings.Builder
|
||||||
asciiArtMargin = 0
|
|
||||||
} else {
|
|
||||||
asciiArtMargin = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
|
asciiArtMargin := getMarginValue(isCompact, 0, 1)
|
||||||
asciiArtStyle := lipgloss.NewStyle().
|
asciiArtStyle := lipgloss.NewStyle().
|
||||||
Bold(true).
|
Bold(true).
|
||||||
Foreground(lipgloss.Color("#7D56F4")).
|
Foreground(lipgloss.Color(ColorPrimary)).
|
||||||
MarginBottom(asciiArtMargin)
|
MarginBottom(asciiArtMargin)
|
||||||
|
|
||||||
var asciiArt string
|
b.WriteString(asciiArtStyle.Render(m.getASCIIArt()))
|
||||||
if shouldUseCompactLayout(m.width, 50) {
|
b.WriteString("\n")
|
||||||
asciiArt = "TUNNEL PLS"
|
|
||||||
} else if isCompact {
|
if !shouldUseCompactLayout(m.width, BreakpointSmall) {
|
||||||
asciiArt = `
|
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))
|
func (m *model) renderSubtitle() string {
|
||||||
b.WriteString("\n")
|
subtitleStyle := lipgloss.NewStyle().
|
||||||
|
Foreground(lipgloss.Color(ColorGray)).
|
||||||
|
Italic(true)
|
||||||
|
|
||||||
if !shouldUseCompactLayout(m.width, 60) {
|
urlStyle := lipgloss.NewStyle().
|
||||||
b.WriteString(subtitleStyle.Render("Secure tunnel service by Bagas • "))
|
Foreground(lipgloss.Color(ColorPrimary)).
|
||||||
b.WriteString(urlStyle.Render("https://fossy.my.id"))
|
Underline(true).
|
||||||
b.WriteString("\n\n")
|
Italic(true)
|
||||||
} else {
|
|
||||||
b.WriteString("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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)
|
boxMaxWidth := getResponsiveWidth(m.width, 10, 40, 80)
|
||||||
var boxPadding int
|
boxPadding := getMarginValue(isCompact, 1, 2)
|
||||||
var boxMargin int
|
boxMargin := getMarginValue(isCompact, 1, 2)
|
||||||
if isCompact {
|
|
||||||
boxPadding = 1
|
|
||||||
boxMargin = 1
|
|
||||||
} else {
|
|
||||||
boxPadding = 2
|
|
||||||
boxMargin = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
responsiveInfoBox := lipgloss.NewStyle().
|
responsiveInfoBox := lipgloss.NewStyle().
|
||||||
Border(lipgloss.RoundedBorder()).
|
Border(lipgloss.RoundedBorder()).
|
||||||
BorderForeground(lipgloss.Color("#7D56F4")).
|
BorderForeground(lipgloss.Color(ColorPrimary)).
|
||||||
Padding(1, boxPadding).
|
Padding(1, boxPadding).
|
||||||
MarginTop(boxMargin).
|
MarginTop(boxMargin).
|
||||||
MarginBottom(boxMargin).
|
MarginBottom(boxMargin).
|
||||||
Width(boxMaxWidth)
|
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().
|
userInfoStyle := lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("#FAFAFA")).
|
Foreground(lipgloss.Color(ColorWhite)).
|
||||||
Bold(true)
|
Bold(true)
|
||||||
|
|
||||||
sectionHeaderStyle := lipgloss.NewStyle().
|
sectionHeaderStyle := lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("#888888")).
|
Foreground(lipgloss.Color(ColorGray)).
|
||||||
Bold(true)
|
Bold(true)
|
||||||
|
|
||||||
addressStyle := lipgloss.NewStyle().
|
addressStyle := lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("#FAFAFA"))
|
Foreground(lipgloss.Color(ColorWhite))
|
||||||
|
|
||||||
var infoContent string
|
urlBoxStyle := lipgloss.NewStyle().
|
||||||
if shouldUseCompactLayout(m.width, 70) {
|
Foreground(lipgloss.Color(ColorSecondary)).
|
||||||
infoContent = fmt.Sprintf("👤 %s\n\n%s\n%s",
|
Bold(true).
|
||||||
userInfoStyle.Render(authenticatedUser),
|
Italic(true)
|
||||||
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())))
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteString(responsiveInfoBox.Render(infoContent))
|
authenticatedUser := m.interaction.user
|
||||||
b.WriteString("\n")
|
tunnelURL := urlBoxStyle.Render(m.getTunnelURL())
|
||||||
|
|
||||||
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 {
|
if isCompact {
|
||||||
featureMargin = 1
|
return fmt.Sprintf("👤 %s\n\n%s\n%s",
|
||||||
} else {
|
userInfoStyle.Render(authenticatedUser),
|
||||||
featureMargin = 2
|
sectionHeaderStyle.Render("🌐 FORWARDING ADDRESS:"),
|
||||||
|
addressStyle.Render(fmt.Sprintf(" %s", tunnelURL)))
|
||||||
}
|
}
|
||||||
|
|
||||||
compactFeatureStyle := lipgloss.NewStyle().
|
return fmt.Sprintf("👤 Authenticated as: %s\n\n%s\n %s",
|
||||||
Foreground(lipgloss.Color("#FAFAFA")).
|
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")
|
||||||
|
|
||||||
|
featureMargin := getMarginValue(isCompact, 1, 2)
|
||||||
|
featureStyle := lipgloss.NewStyle().
|
||||||
|
Foreground(lipgloss.Color(ColorWhite)).
|
||||||
MarginLeft(featureMargin)
|
MarginLeft(featureMargin)
|
||||||
|
|
||||||
var commandsText string
|
keyHintStyle := lipgloss.NewStyle().
|
||||||
var quitText string
|
Foreground(lipgloss.Color(ColorPrimary)).
|
||||||
if shouldUseCompactLayout(m.width, 60) {
|
Bold(true)
|
||||||
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]"))
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteString(compactFeatureStyle.Render(commandsText))
|
commands := m.getActionCommands(keyHintStyle)
|
||||||
|
b.WriteString(featureStyle.Render(commands.commandsText))
|
||||||
b.WriteString("\n")
|
b.WriteString("\n")
|
||||||
b.WriteString(compactFeatureStyle.Render(quitText))
|
b.WriteString(featureStyle.Render(commands.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"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.String()
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,6 +42,25 @@ type model struct {
|
|||||||
height int
|
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 {
|
func (m *model) getTunnelURL() string {
|
||||||
if m.tunnelType == types.TunnelTypeHTTP {
|
if m.tunnelType == types.TunnelTypeHTTP {
|
||||||
return buildURL(m.protocol, m.interaction.slug.String(), m.domain)
|
return buildURL(m.protocol, m.interaction.slug.String(), m.domain)
|
||||||
|
|||||||
+182
-140
@@ -21,7 +21,7 @@ func (m *model) slugUpdate(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch msg.String() {
|
switch msg.String() {
|
||||||
case "esc":
|
case "esc", "ctrl+c":
|
||||||
m.editingSlug = false
|
m.editingSlug = false
|
||||||
m.slugError = ""
|
m.slugError = ""
|
||||||
return m, tea.Batch(tea.ClearScreen, textinput.Blink)
|
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.editingSlug = false
|
||||||
m.slugError = ""
|
m.slugError = ""
|
||||||
return m, tea.Batch(tea.ClearScreen, textinput.Blink)
|
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:
|
default:
|
||||||
if key.Matches(msg, m.keymap.random) {
|
if key.Matches(msg, m.keymap.random) {
|
||||||
newSubdomain, err := m.randomizer.String(20)
|
newSubdomain, err := m.randomizer.String(20)
|
||||||
@@ -51,8 +47,6 @@ func (m *model) slugUpdate(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||||||
return m, cmd
|
return m, cmd
|
||||||
}
|
}
|
||||||
m.slugInput.SetValue(newSubdomain)
|
m.slugInput.SetValue(newSubdomain)
|
||||||
m.slugError = ""
|
|
||||||
m.slugInput, cmd = m.slugInput.Update(msg)
|
|
||||||
}
|
}
|
||||||
m.slugError = ""
|
m.slugError = ""
|
||||||
m.slugInput, cmd = m.slugInput.Update(msg)
|
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 {
|
func (m *model) slugView() string {
|
||||||
isCompact := shouldUseCompactLayout(m.width, 70)
|
isCompact := shouldUseCompactLayout(m.width, BreakpointMedium)
|
||||||
isVeryCompact := shouldUseCompactLayout(m.width, 50)
|
isVeryCompact := shouldUseCompactLayout(m.width, BreakpointTiny)
|
||||||
|
|
||||||
var boxPadding int
|
var b strings.Builder
|
||||||
var boxMargin int
|
b.WriteString(m.renderSlugTitle(isVeryCompact))
|
||||||
if isVeryCompact {
|
|
||||||
boxPadding = 1
|
if m.tunnelType != types.TunnelTypeHTTP {
|
||||||
boxMargin = 1
|
b.WriteString(m.renderTCPWarning(isVeryCompact, isCompact))
|
||||||
} else if isCompact {
|
return b.String()
|
||||||
boxPadding = 1
|
|
||||||
boxMargin = 1
|
|
||||||
} else {
|
|
||||||
boxPadding = 2
|
|
||||||
boxMargin = 2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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().
|
titleStyle := lipgloss.NewStyle().
|
||||||
Bold(true).
|
Bold(true).
|
||||||
Foreground(lipgloss.Color("#7D56F4")).
|
Foreground(lipgloss.Color(ColorPrimary)).
|
||||||
PaddingTop(1).
|
PaddingTop(1).
|
||||||
PaddingBottom(1)
|
PaddingBottom(1)
|
||||||
|
|
||||||
instructionStyle := lipgloss.NewStyle().
|
title := "🔧 Edit Subdomain"
|
||||||
Foreground(lipgloss.Color("#FAFAFA")).
|
|
||||||
MarginTop(1)
|
|
||||||
|
|
||||||
inputBoxStyle := lipgloss.NewStyle().
|
|
||||||
Border(lipgloss.RoundedBorder()).
|
|
||||||
BorderForeground(lipgloss.Color("#7D56F4")).
|
|
||||||
Padding(1, boxPadding).
|
|
||||||
MarginTop(boxMargin).
|
|
||||||
MarginBottom(boxMargin)
|
|
||||||
|
|
||||||
helpStyle := lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("#666666")).
|
|
||||||
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)
|
|
||||||
|
|
||||||
rulesBoxWidth := getResponsiveWidth(m.width, 10, 30, 60)
|
|
||||||
rulesBoxStyle := lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("#FAFAFA")).
|
|
||||||
Border(lipgloss.RoundedBorder()).
|
|
||||||
BorderForeground(lipgloss.Color("#7D56F4")).
|
|
||||||
Padding(0, boxPadding).
|
|
||||||
MarginTop(1).
|
|
||||||
MarginBottom(1).
|
|
||||||
Width(rulesBoxWidth)
|
|
||||||
|
|
||||||
var b strings.Builder
|
|
||||||
var title string
|
|
||||||
if isVeryCompact {
|
if isVeryCompact {
|
||||||
title = "Edit Subdomain"
|
title = "Edit Subdomain"
|
||||||
} else {
|
|
||||||
title = "🔧 Edit Subdomain"
|
|
||||||
}
|
}
|
||||||
b.WriteString(titleStyle.Render(title))
|
|
||||||
b.WriteString("\n\n")
|
|
||||||
|
|
||||||
if m.tunnelType != types.TunnelTypeHTTP {
|
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)
|
warningBoxWidth := getResponsiveWidth(m.width, 10, 30, 60)
|
||||||
|
|
||||||
warningBoxStyle := lipgloss.NewStyle().
|
warningBoxStyle := lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("#FFA500")).
|
Foreground(lipgloss.Color(ColorWarning)).
|
||||||
Background(lipgloss.Color("#3D2000")).
|
Background(lipgloss.Color(ColorWarningBg)).
|
||||||
Bold(true).
|
Bold(true).
|
||||||
Border(lipgloss.RoundedBorder()).
|
Border(lipgloss.RoundedBorder()).
|
||||||
BorderForeground(lipgloss.Color("#FFA500")).
|
BorderForeground(lipgloss.Color(ColorWarning)).
|
||||||
Padding(1, boxPadding).
|
Padding(1, boxPadding).
|
||||||
MarginTop(boxMargin).
|
MarginTop(boxMargin).
|
||||||
MarginBottom(boxMargin).
|
MarginBottom(boxMargin).
|
||||||
Width(warningBoxWidth)
|
Width(warningBoxWidth)
|
||||||
|
|
||||||
var warningText string
|
helpStyle := lipgloss.NewStyle().
|
||||||
if isVeryCompact {
|
Foreground(lipgloss.Color(ColorDarkGray)).
|
||||||
warningText = "⚠️ TCP tunnels don't support custom subdomains."
|
Italic(true).
|
||||||
} else {
|
MarginTop(1)
|
||||||
warningText = "⚠️ TCP tunnels cannot have custom subdomains. Only HTTP/HTTPS tunnels support subdomain customization."
|
|
||||||
}
|
warningText := m.getTCPWarningText(isVeryCompact)
|
||||||
|
helpText := m.getTCPHelpText(isVeryCompact)
|
||||||
|
|
||||||
|
var b strings.Builder
|
||||||
b.WriteString(warningBoxStyle.Render(warningText))
|
b.WriteString(warningBoxStyle.Render(warningText))
|
||||||
b.WriteString("\n\n")
|
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
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 -"
|
|
||||||
}
|
|
||||||
b.WriteString(rulesBoxStyle.Render(rulesContent))
|
|
||||||
b.WriteString("\n")
|
|
||||||
|
|
||||||
var instruction string
|
|
||||||
if isVeryCompact {
|
|
||||||
instruction = "Custom subdomain:"
|
|
||||||
} else {
|
|
||||||
instruction = "Enter your custom subdomain:"
|
|
||||||
}
|
|
||||||
b.WriteString(instructionStyle.Render(instruction))
|
|
||||||
b.WriteString("\n")
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
previewURL := buildURL(m.protocol, m.slugInput.Value(), m.domain)
|
|
||||||
previewWidth := getResponsiveWidth(m.width, 10, 30, 80)
|
|
||||||
|
|
||||||
if len(previewURL) > previewWidth-10 {
|
|
||||||
previewURL = truncateString(previewURL, previewWidth-10)
|
|
||||||
}
|
|
||||||
|
|
||||||
previewStyle := lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("#04B575")).
|
|
||||||
Italic(true).
|
|
||||||
Width(previewWidth)
|
|
||||||
b.WriteString(previewStyle.Render(fmt.Sprintf("Preview: %s", previewURL)))
|
|
||||||
b.WriteString("\n")
|
|
||||||
|
|
||||||
var helpText string
|
|
||||||
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))
|
b.WriteString(helpStyle.Render(helpText))
|
||||||
|
|
||||||
return b.String()
|
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(ColorWhite)).
|
||||||
|
Border(lipgloss.RoundedBorder()).
|
||||||
|
BorderForeground(lipgloss.Color(ColorPrimary)).
|
||||||
|
Padding(0, boxPadding).
|
||||||
|
MarginTop(1).
|
||||||
|
MarginBottom(1).
|
||||||
|
Width(rulesBoxWidth)
|
||||||
|
|
||||||
|
rulesContent := m.getRulesContent(isVeryCompact, isCompact)
|
||||||
|
return rulesBoxStyle.Render(rulesContent) + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) getRulesContent(isVeryCompact, isCompact bool) string {
|
||||||
|
if isVeryCompact {
|
||||||
|
return "Rules:\n3-20 chars\na-z, 0-9, -\nNo leading/trailing -"
|
||||||
|
}
|
||||||
|
|
||||||
|
if isCompact {
|
||||||
|
return "📋 Rules:\n • 3-20 chars\n • a-z, 0-9, -\n • No leading/trailing -"
|
||||||
|
}
|
||||||
|
|
||||||
|
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:"
|
||||||
|
}
|
||||||
|
|
||||||
|
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 != "" {
|
||||||
|
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 isVeryCompact {
|
||||||
|
previewURL = truncateString(previewURL, previewWidth-10)
|
||||||
|
}
|
||||||
|
|
||||||
|
previewStyle := lipgloss.NewStyle().
|
||||||
|
Foreground(lipgloss.Color(ColorSecondary)).
|
||||||
|
Italic(true).
|
||||||
|
Width(previewWidth)
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
return helpStyle.Render(helpText)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPaddingValue(isVeryCompact, isCompact bool) int {
|
||||||
|
if isVeryCompact || isCompact {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user