feat(client): show local “[order] …” entry after successful submit; introduce server line msg type; WIP reader state in broken snapshot
- Add serverLineMsg type for future server line handling - On successful orderSubmittedMsg with total: - Derive item name from cached menu and append a formatted “[order] Name ordered N × Item ($Total)” to broadcasts - Keep broadcasts capped at 10 entries - Maintain existing ack handling when no total provided Also add a WIP snapshot (main.go.broken): - Replace broadcastListening flag with waitingForMenu and waitingForOrderResp - On connect, start read loop via readServerLineCmd(reader) - When submitting an order, set waitingForOrderResp and call submitOrderCmd without passing reader - After orderSubmittedMsg (success or error), resume listenForBroadcastsCmd if conn/reader present Note: main.go.broken reflects an in-progress refactor toward a single read loop and explicit waiting states.
This commit is contained in:
parent
aee33003bf
commit
e523297d5f
23
main.go
23
main.go
@ -37,8 +37,9 @@ type (
|
||||
total float64
|
||||
err error
|
||||
}
|
||||
broadcastMsg string
|
||||
statusMsg string
|
||||
broadcastMsg string
|
||||
statusMsg string
|
||||
serverLineMsg string
|
||||
)
|
||||
|
||||
type FormFields struct {
|
||||
@ -189,6 +190,24 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.err = nil
|
||||
if msg.total > 0 {
|
||||
m.status = fmt.Sprintf("Order submitted. Total: $%.2f", msg.total)
|
||||
|
||||
if m.lastOrder != nil {
|
||||
var itemName string
|
||||
for _, it := range m.menu {
|
||||
if it.ID == m.lastOrder.ItemID {
|
||||
itemName = it.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
if itemName != "" {
|
||||
broadcastText := fmt.Sprintf("[order] %s ordered %d × %s ($%.2f)",
|
||||
m.lastOrder.Name, m.lastOrder.Quantity, itemName, msg.total)
|
||||
m.broadcasts = append(m.broadcasts, broadcastText)
|
||||
if len(m.broadcasts) > 10 {
|
||||
m.broadcasts = m.broadcasts[1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if msg.ack != "" {
|
||||
m.status = fmt.Sprintf("Order submitted. Server says: %s", msg.ack)
|
||||
}
|
||||
|
||||
592
main.go.broken
Normal file
592
main.go.broken
Normal file
@ -0,0 +1,592 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/huh"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
type menuItem struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Price float64 `json:"price"`
|
||||
}
|
||||
|
||||
// order represents the payload we submit back to the server.
|
||||
|
||||
// messages used by Bubble Tea
|
||||
type (
|
||||
connectedMsg struct{ conn net.Conn }
|
||||
menuLoadedMsg struct {
|
||||
items []menuItem
|
||||
err error
|
||||
}
|
||||
orderSubmittedMsg struct {
|
||||
ack string
|
||||
total float64
|
||||
err error
|
||||
}
|
||||
broadcastMsg string
|
||||
statusMsg string
|
||||
serverLineMsg string
|
||||
)
|
||||
|
||||
type FormFields struct {
|
||||
name string
|
||||
itemID string
|
||||
quantityStr string
|
||||
confirm bool
|
||||
}
|
||||
|
||||
// model holds the TUI state.
|
||||
type model struct {
|
||||
host string
|
||||
conn net.Conn
|
||||
|
||||
title string
|
||||
status string
|
||||
loading bool
|
||||
err error
|
||||
lastOrder *order
|
||||
broadcasts []string
|
||||
|
||||
form *huh.Form
|
||||
formFields *FormFields
|
||||
menu []menuItem
|
||||
name string
|
||||
itemID string
|
||||
quantityStr string
|
||||
confirm bool
|
||||
|
||||
width int
|
||||
height int
|
||||
|
||||
reader *bufio.Reader
|
||||
waitingForMenu bool
|
||||
waitingForOrderResp bool
|
||||
}
|
||||
|
||||
// initialModel creates a base model.
|
||||
func initialModel(host string) model {
|
||||
return model{
|
||||
host: host,
|
||||
title: "Order Console",
|
||||
formFields: &FormFields{},
|
||||
}
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
// Connect on startup
|
||||
return connectCmd(m.host)
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmds []tea.Cmd
|
||||
|
||||
// If a form is active, delegate to it first.
|
||||
if m.form != nil {
|
||||
var cmd tea.Cmd
|
||||
form, cmd := m.form.Update(msg)
|
||||
|
||||
if f, ok := form.(*huh.Form); ok {
|
||||
m.form = f
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
||||
if m.form.State == huh.StateCompleted {
|
||||
// Parse and submit order if confirmed.
|
||||
qty, err := strconv.Atoi(strings.TrimSpace(m.formFields.quantityStr))
|
||||
if err != nil || qty <= 0 {
|
||||
m.err = fmt.Errorf("invalid quantity: %v", m.formFields.quantityStr)
|
||||
m.form = nil
|
||||
return m, nil
|
||||
}
|
||||
ord := &order{
|
||||
Name: strings.TrimSpace(m.formFields.name),
|
||||
ItemID: m.formFields.itemID,
|
||||
Quantity: qty,
|
||||
}
|
||||
m.lastOrder = ord
|
||||
m.form = nil
|
||||
|
||||
if m.formFields.confirm {
|
||||
if m.conn == nil {
|
||||
m.status = "Not connected. Unable to submit order."
|
||||
return m, nil
|
||||
}
|
||||
m.err = nil
|
||||
m.loading = true
|
||||
m.status = "Submitting order..."
|
||||
m.waitingForOrderResp = true
|
||||
return m, submitOrderCmd(m.conn, *ord)
|
||||
}
|
||||
m.status = "Order canceled."
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
if m.form.State == huh.StateAborted {
|
||||
m.status = "Order form aborted."
|
||||
m.form = nil
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case connectedMsg:
|
||||
m.conn = msg.conn
|
||||
m.reader = bufio.NewReader(m.conn)
|
||||
m.status = fmt.Sprintf("Connected to %s", m.host)
|
||||
|
||||
_ = m.conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
|
||||
for i := 0; i < 2; i++ {
|
||||
if _, err := m.reader.ReadString('\n'); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
_ = m.conn.SetReadDeadline(time.Time{})
|
||||
|
||||
return m, readServerLineCmd(m.reader)
|
||||
|
||||
case menuLoadedMsg:
|
||||
m.loading = false
|
||||
if msg.err != nil {
|
||||
m.err = msg.err
|
||||
m.status = "Failed to load menu."
|
||||
return m, nil
|
||||
}
|
||||
m.err = nil
|
||||
m.menu = msg.items
|
||||
m.status = "Menu loaded."
|
||||
|
||||
var cmds []tea.Cmd
|
||||
m.form = m.buildForm()
|
||||
cmds = append(cmds, m.form.Init())
|
||||
|
||||
if !m.broadcastListening {
|
||||
m.broadcastListening = true
|
||||
cmds = append(cmds, listenForBroadcastsCmd(m.conn, m.reader))
|
||||
}
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
|
||||
case orderSubmittedMsg:
|
||||
m.loading = false
|
||||
if msg.err != nil {
|
||||
m.err = msg.err
|
||||
m.status = "Order submission failed."
|
||||
if m.conn != nil && m.reader != nil {
|
||||
return m, listenForBroadcastsCmd(m.conn, m.reader)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
m.err = nil
|
||||
if msg.total > 0 {
|
||||
m.status = fmt.Sprintf("Order submitted. Total: $%.2f", msg.total)
|
||||
} else if msg.ack != "" {
|
||||
m.status = fmt.Sprintf("Order submitted. Server says: %s", msg.ack)
|
||||
}
|
||||
if m.conn != nil && m.reader != nil {
|
||||
return m, listenForBroadcastsCmd(m.conn, m.reader)
|
||||
}
|
||||
return m, nil
|
||||
|
||||
case broadcastMsg:
|
||||
msgText := string(msg)
|
||||
if strings.HasPrefix(msgText, "[order]") {
|
||||
m.broadcasts = append(m.broadcasts, msgText)
|
||||
if len(m.broadcasts) > 10 {
|
||||
m.broadcasts = m.broadcasts[1:]
|
||||
}
|
||||
}
|
||||
return m, listenForBroadcastsCmd(m.conn, m.reader)
|
||||
|
||||
case statusMsg:
|
||||
m.status = string(msg)
|
||||
return m, nil
|
||||
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "q", "ctrl+c", "esc":
|
||||
if m.conn != nil {
|
||||
_ = m.conn.Close()
|
||||
}
|
||||
return m, tea.Quit
|
||||
case "r":
|
||||
// Reconnect
|
||||
if m.conn != nil {
|
||||
_ = m.conn.Close()
|
||||
m.conn = nil
|
||||
}
|
||||
m.status = "Reconnecting..."
|
||||
return m, connectCmd(m.host)
|
||||
case "n":
|
||||
if m.loading || m.form != nil {
|
||||
return m, nil
|
||||
}
|
||||
if m.conn == nil {
|
||||
m.status = "Not connected. Press 'r' to reconnect."
|
||||
return m, nil
|
||||
}
|
||||
m.err = nil
|
||||
if len(m.menu) > 0 {
|
||||
m.form = m.buildForm()
|
||||
return m, m.form.Init()
|
||||
}
|
||||
m.loading = true
|
||||
m.status = "Loading menu..."
|
||||
return m, fetchMenuCmd(m.conn, m.reader)
|
||||
}
|
||||
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m model) renderHeader() string {
|
||||
titleStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("212"))
|
||||
hostStyle := lipgloss.NewStyle().Faint(true)
|
||||
|
||||
title := titleStyle.Render(m.title)
|
||||
host := hostStyle.Render(m.host)
|
||||
|
||||
header := lipgloss.JoinVertical(lipgloss.Center, title, host)
|
||||
return lipgloss.NewStyle().Width(m.width).Align(lipgloss.Center).Render(header)
|
||||
}
|
||||
|
||||
func (m model) renderLeftColumn() string {
|
||||
lines := []string{}
|
||||
|
||||
if m.loading {
|
||||
lines = append(lines, "Status: "+lipgloss.NewStyle().Foreground(lipgloss.Color("178")).Render("Loading..."))
|
||||
} else if m.status != "" {
|
||||
lines = append(lines, "Status: "+m.status)
|
||||
}
|
||||
|
||||
if m.err != nil {
|
||||
lines = append(lines, lipgloss.NewStyle().Foreground(lipgloss.Color("9")).Render(fmt.Sprintf("Error: %v", m.err)))
|
||||
}
|
||||
|
||||
if m.lastOrder != nil {
|
||||
lines = append(lines, "", lipgloss.NewStyle().Bold(true).Render("Last Order:"))
|
||||
lines = append(lines, fmt.Sprintf(" Name: %s", m.lastOrder.Name))
|
||||
var label string
|
||||
for _, it := range m.menu {
|
||||
if it.ID == m.lastOrder.ItemID {
|
||||
label = it.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
if label != "" {
|
||||
lines = append(lines, fmt.Sprintf(" Item: %s", label))
|
||||
} else {
|
||||
lines = append(lines, fmt.Sprintf(" Item: %s", m.lastOrder.ItemID))
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf(" Quantity: %d", m.lastOrder.Quantity))
|
||||
}
|
||||
|
||||
content := lipgloss.JoinVertical(lipgloss.Left, lines...)
|
||||
return lipgloss.NewStyle().
|
||||
Width(m.width/2 - 2).
|
||||
Height(m.height - 6).
|
||||
Padding(1).
|
||||
Render(content)
|
||||
}
|
||||
|
||||
func (m model) renderRightColumn() string {
|
||||
lines := []string{}
|
||||
headerStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("212"))
|
||||
lines = append(lines, headerStyle.Render("Recent Orders:"))
|
||||
lines = append(lines, "")
|
||||
|
||||
if len(m.broadcasts) == 0 {
|
||||
lines = append(lines, lipgloss.NewStyle().Faint(true).Render("No orders yet..."))
|
||||
} else {
|
||||
bulletStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("141"))
|
||||
nameStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("86")).Bold(true)
|
||||
itemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("117"))
|
||||
priceStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("220")).Bold(true)
|
||||
|
||||
for _, b := range m.broadcasts {
|
||||
msg := strings.TrimPrefix(b, "[order] ")
|
||||
parts := strings.SplitN(msg, " ordered ", 2)
|
||||
if len(parts) == 2 {
|
||||
customer := parts[0]
|
||||
orderDetails := parts[1]
|
||||
|
||||
line := fmt.Sprintf("%s %s ordered %s",
|
||||
bulletStyle.Render("•"),
|
||||
nameStyle.Render(customer),
|
||||
itemStyle.Render(orderDetails))
|
||||
|
||||
if idx := strings.Index(orderDetails, "($"); idx != -1 {
|
||||
priceStart := idx
|
||||
priceEnd := strings.Index(orderDetails[priceStart:], ")")
|
||||
if priceEnd != -1 {
|
||||
priceEnd += priceStart + 1
|
||||
beforePrice := orderDetails[:priceStart]
|
||||
priceText := orderDetails[priceStart:priceEnd]
|
||||
|
||||
line = fmt.Sprintf("%s %s ordered %s %s",
|
||||
bulletStyle.Render("•"),
|
||||
nameStyle.Render(customer),
|
||||
itemStyle.Render(beforePrice),
|
||||
priceStyle.Render(priceText))
|
||||
}
|
||||
}
|
||||
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content := lipgloss.JoinVertical(lipgloss.Left, lines...)
|
||||
return lipgloss.NewStyle().
|
||||
Width(m.width/2 - 2).
|
||||
Height(m.height - 6).
|
||||
Padding(1).
|
||||
Render(content)
|
||||
}
|
||||
|
||||
func (m model) renderFooter() string {
|
||||
connStatus := ""
|
||||
if m.conn != nil {
|
||||
connStatus = lipgloss.NewStyle().Foreground(lipgloss.Color("10")).Render("● Connected")
|
||||
} else {
|
||||
connStatus = lipgloss.NewStyle().Foreground(lipgloss.Color("9")).Render("● Disconnected")
|
||||
}
|
||||
|
||||
controls := lipgloss.NewStyle().Faint(true).Render("n: New Order r: Reconnect q: Quit")
|
||||
|
||||
leftSide := connStatus
|
||||
rightSide := controls
|
||||
|
||||
footer := lipgloss.JoinHorizontal(lipgloss.Top,
|
||||
lipgloss.NewStyle().Width(m.width/2).Render(leftSide),
|
||||
lipgloss.NewStyle().Width(m.width/2).Align(lipgloss.Right).Render(rightSide),
|
||||
)
|
||||
|
||||
return lipgloss.NewStyle().Width(m.width).Render(footer)
|
||||
}
|
||||
|
||||
func (m model) View() string {
|
||||
if m.form != nil {
|
||||
return m.form.View()
|
||||
}
|
||||
|
||||
if m.width == 0 || m.height == 0 {
|
||||
return "Loading..."
|
||||
}
|
||||
|
||||
header := m.renderHeader()
|
||||
|
||||
leftCol := m.renderLeftColumn()
|
||||
rightCol := m.renderRightColumn()
|
||||
body := lipgloss.JoinHorizontal(lipgloss.Top, leftCol, rightCol)
|
||||
|
||||
footer := m.renderFooter()
|
||||
|
||||
return lipgloss.JoinVertical(lipgloss.Left,
|
||||
header,
|
||||
"",
|
||||
body,
|
||||
"",
|
||||
footer,
|
||||
)
|
||||
}
|
||||
|
||||
// buildForm constructs the order form: Input (name) -> Select (menu) -> Input (qty) -> Confirm.
|
||||
func (m *model) buildForm() *huh.Form {
|
||||
opts := make([]huh.Option[string], 0, len(m.menu))
|
||||
for _, it := range m.menu {
|
||||
opts = append(opts, huh.NewOption(fmt.Sprintf("%s - $%.2f", it.Name, it.Price), it.ID))
|
||||
}
|
||||
|
||||
// Reset bound fields for a fresh form
|
||||
m.formFields.name = ""
|
||||
m.formFields.itemID = ""
|
||||
m.formFields.quantityStr = ""
|
||||
m.formFields.confirm = false
|
||||
|
||||
f := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewInput().
|
||||
Title("Your name").
|
||||
Prompt("> ").
|
||||
Placeholder("Jane Doe").
|
||||
Value(&m.formFields.name).
|
||||
Validate(func(s string) error {
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return errors.New("name is required")
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
huh.NewSelect[string]().
|
||||
Title("Menu item").
|
||||
Options(opts...).
|
||||
Value(&m.formFields.itemID).
|
||||
Validate(func(v string) error {
|
||||
if v == "" {
|
||||
return errors.New("please select a menu item")
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
huh.NewInput().
|
||||
Title("Quantity").
|
||||
Prompt("> ").
|
||||
Placeholder("1").
|
||||
Value(&m.formFields.quantityStr).
|
||||
Validate(func(s string) error {
|
||||
n, err := strconv.Atoi(strings.TrimSpace(s))
|
||||
if err != nil || n <= 0 {
|
||||
return errors.New("enter a positive integer")
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
huh.NewConfirm().
|
||||
Title("Place order?").
|
||||
Affirmative("Yes").
|
||||
Negative("No").
|
||||
Value(&m.formFields.confirm),
|
||||
),
|
||||
)
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// connectCmd connects to the TCP server.
|
||||
func connectCmd(addr string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
conn, err := net.DialTimeout("tcp", addr, 3*time.Second)
|
||||
if err != nil {
|
||||
return statusMsg(fmt.Sprintf("Connect failed: %v", err))
|
||||
}
|
||||
|
||||
return connectedMsg{conn: conn}
|
||||
}
|
||||
}
|
||||
|
||||
// fetchMenuCmd asks the server for a menu via the TCP connection.
|
||||
// Protocol (proposed):
|
||||
// - client: "MENU\n"
|
||||
// - server: single line JSON array: [{"id":"x","name":"..."}]\n
|
||||
func fetchMenuCmd(conn net.Conn, reader *bufio.Reader) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
if conn == nil {
|
||||
return menuLoadedMsg{err: errors.New("not connected")}
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintln(conn, "MENU"); err != nil {
|
||||
return menuLoadedMsg{err: fmt.Errorf("send MENU: %w", err)}
|
||||
}
|
||||
|
||||
_ = conn.SetReadDeadline(time.Now().Add(3 * time.Second))
|
||||
defer func() { _ = conn.SetReadDeadline(time.Time{}) }()
|
||||
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return menuLoadedMsg{err: fmt.Errorf("read MENU: %w", err)}
|
||||
}
|
||||
line = strings.TrimRight(line, "\r\n")
|
||||
if strings.HasPrefix(line, "[error]") {
|
||||
return menuLoadedMsg{err: fmt.Errorf("server: %s", line)}
|
||||
}
|
||||
|
||||
var items []menuItem
|
||||
if err := json.Unmarshal([]byte(line), &items); err != nil {
|
||||
return menuLoadedMsg{err: fmt.Errorf("invalid menu JSON: %w", err)}
|
||||
}
|
||||
return menuLoadedMsg{items: items}
|
||||
}
|
||||
}
|
||||
|
||||
// submitOrderCmd sends the order over TCP.
|
||||
// Protocol (proposed):
|
||||
// - client: "ORDER <json>\n"
|
||||
// - server: a single line acknowledgement (freeform), e.g. "OK\n"
|
||||
func submitOrderCmd(conn net.Conn, ord order, reader *bufio.Reader) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
if conn == nil {
|
||||
return orderSubmittedMsg{err: errors.New("not connected")}
|
||||
}
|
||||
b, err := json.Marshal(ord)
|
||||
if err != nil {
|
||||
return orderSubmittedMsg{err: fmt.Errorf("marshal order: %w", err)}
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintf(conn, "ORDER %s\n", string(b)); err != nil {
|
||||
return orderSubmittedMsg{err: fmt.Errorf("send ORDER: %w", err)}
|
||||
}
|
||||
|
||||
_ = conn.SetReadDeadline(time.Now().Add(3 * time.Second))
|
||||
defer func() { _ = conn.SetReadDeadline(time.Time{}) }()
|
||||
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return orderSubmittedMsg{err: fmt.Errorf("read ORDER ack: %w", err)}
|
||||
}
|
||||
line = strings.TrimRight(line, "\r\n")
|
||||
parts := strings.Split(line, "|")
|
||||
ack := parts[0]
|
||||
var total float64
|
||||
if len(parts) > 1 {
|
||||
if t, err := strconv.ParseFloat(parts[1], 64); err == nil {
|
||||
total = t
|
||||
}
|
||||
}
|
||||
return orderSubmittedMsg{ack: ack, total: total}
|
||||
}
|
||||
}
|
||||
|
||||
func listenForBroadcastsCmd(conn net.Conn, reader *bufio.Reader) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
if conn == nil || reader == nil {
|
||||
return nil
|
||||
}
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return statusMsg(fmt.Sprintf("Connection closed: %v", err))
|
||||
}
|
||||
return broadcastMsg(strings.TrimRight(line, "\r\n"))
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var (
|
||||
host string
|
||||
serverOnly bool
|
||||
)
|
||||
flag.StringVar(&host, "host", "localhost:9000", "host:port to connect to or bind the server on")
|
||||
flag.BoolVar(&serverOnly, "server", false, "run only the server")
|
||||
flag.Parse()
|
||||
|
||||
// If requested, start the TCP server (chat server as-is).
|
||||
if serverOnly {
|
||||
if err := startTCPServer(host); err != nil {
|
||||
fmt.Println("Server error:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Client TUI
|
||||
m := initialModel(host)
|
||||
p := tea.NewProgram(m, tea.WithAltScreen())
|
||||
if _, err := p.Run(); err != nil {
|
||||
fmt.Println("error:", err)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user