diff --git a/ui/constants.go b/ui/constants.go index d5746b9..d9858e2 100644 --- a/ui/constants.go +++ b/ui/constants.go @@ -15,8 +15,9 @@ const ( MAX_WIDTH = 80 PRIMARY_COLOR = "#B8BABA" SECONDARY_COLOR = "#626262" + DARK_COLOR = "#232323" ELEMENT_COLOR = "#EE9F40" - SECONDARY_ELEMENT_COLOR = "#EE9F70" + SECONDARY_ELEMENT_COLOR = "#e87d3e" ERROR_COLOR = "#CC0000" WARNING_COLOR = "#EE9F5C" CHECK_COLOR = "#34B233" @@ -65,10 +66,12 @@ var Keys = KeyMap{ FileListUp: key.NewBinding( key.WithKeys("up", "k"), key.WithHelp("(↑/k)", "file summary up"), + key.WithDisabled(), ), FileListDown: key.NewBinding( key.WithKeys("down", "j"), key.WithHelp("(↓/j)", "file summary down"), + key.WithDisabled(), ), } diff --git a/ui/filetable/filetable.go b/ui/filetable/filetable.go index 93e328d..0093f8c 100644 --- a/ui/filetable/filetable.go +++ b/ui/filetable/filetable.go @@ -12,7 +12,7 @@ import ( ) const ( - maxTableHeight = 4 + defaultMaxTableHeight = 4 nameColumnWidthFactor float64 = 0.8 sizeColumnWidthFactor float64 = 1 - nameColumnWidthFactor ) @@ -30,30 +30,34 @@ type fileRow struct { } type Model struct { - Width int - rows []fileRow - table table.Model + Width int + MaxHeight int + rows []fileRow + table table.Model + tableStyles table.Styles } func New(opts ...Option) Model { m := Model{ + MaxHeight: defaultMaxTableHeight, table: table.New( table.WithFocused(true), - table.WithHeight(maxTableHeight), + table.WithHeight(defaultMaxTableHeight), ), } s := table.DefaultStyles() s.Header = s.Header. BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")). + BorderForeground(lipgloss.Color(ui.SECONDARY_COLOR)). BorderBottom(true). Bold(true) s.Selected = s.Selected. - Foreground(lipgloss.Color("229")). - Background(lipgloss.Color("57")). + Foreground(lipgloss.Color(ui.DARK_COLOR)). + Background(lipgloss.Color(ui.SECONDARY_ELEMENT_COLOR)). Bold(false) - m.table.SetStyles(s) + m.tableStyles = s + m.table.SetStyles(m.tableStyles) m.updateColumns() for _, opt := range opts { @@ -75,12 +79,19 @@ func WithFiles(filePaths []string) Option { } m.rows = append(m.rows, fileRow{path: filePath, formattedSize: formattedSize}) } - m.table.SetHeight(int(math.Min(maxTableHeight, float64(len(filePaths))))) + m.table.SetHeight(int(math.Min(float64(m.MaxHeight), float64(len(filePaths))))) m.updateColumns() m.updateRows() } } +func WithMaxHeight(height int) Option { + return func(m *Model) { + m.MaxHeight = height + m.updateRows() + } +} + func (m *Model) getMaxWidth() int { return int(math.Min(ui.MAX_WIDTH-2*ui.MARGIN, float64(m.Width))) } @@ -112,6 +123,16 @@ func (Model) Init() tea.Cmd { return nil } +func (m Model) Finalize() tea.Model { + m.table.Blur() + + s := m.tableStyles + s.Selected = s.Selected.UnsetBackground().UnsetForeground() + m.table.SetStyles(s) + + return m +} + func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd diff --git a/ui/receiver/receiver.go b/ui/receiver/receiver.go index 2be05fe..fb45fa2 100644 --- a/ui/receiver/receiver.go +++ b/ui/receiver/receiver.go @@ -2,6 +2,7 @@ package receiver import ( "fmt" + "math" "os" "time" @@ -11,14 +12,13 @@ import ( "github.com/SpatiumPortae/portal/internal/semver" "github.com/SpatiumPortae/portal/protocol/transfer" "github.com/SpatiumPortae/portal/ui" + "github.com/SpatiumPortae/portal/ui/filetable" "github.com/SpatiumPortae/portal/ui/transferprogress" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/muesli/reflow/indent" - "github.com/muesli/reflow/wordwrap" ) // ------------------------------------------------------ Ui State ----------------------------------------------------- @@ -79,6 +79,7 @@ type model struct { width int spinner spinner.Model transferProgress transferprogress.Model + fileTable filetable.Model help help.Model keys ui.KeyMap } @@ -88,6 +89,7 @@ func New(addr string, password string, opts ...Option) *tea.Program { m := model{ transferProgress: transferprogress.New(), msgs: make(chan interface{}, 10), + fileTable: filetable.New(), password: password, rendezvousAddr: addr, help: help.New(), @@ -174,12 +176,17 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { time.Since(m.transferProgress.TransferStartTime).Round(time.Millisecond).String(), ui.ByteCountSI(m.transferProgress.TransferSpeedEstimateBps), ) + + filetable.WithMaxHeight(math.MaxInt)(&m.fileTable) + m.fileTable = m.fileTable.Finalize().(filetable.Model) return m, ui.TaskCmd(message, tea.Batch(m.spinner.Tick, decompressCmd(msg.temp))) case decompressionDoneMsg: m.state = showFinished m.receivedFiles = msg.filenames m.decompressedPayloadSize = msg.decompressedPayloadSize + + filetable.WithFiles(m.receivedFiles)(&m.fileTable) return m, ui.QuitCmd() case ui.ErrorMsg: @@ -192,13 +199,19 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, m.keys.Quit): return m, tea.Quit } - return m, nil + + fileTableModel, fileTableCmd := m.fileTable.Update(msg) + m.fileTable = fileTableModel.(filetable.Model) + + return m, fileTableCmd case tea.WindowSizeMsg: m.width = msg.Width - transferProgressModel, cmd := m.transferProgress.Update(msg) + transferProgressModel, transferProgressCmd := m.transferProgress.Update(msg) m.transferProgress = transferProgressModel.(transferprogress.Model) - return m, cmd + fileTableModel, fileTableCmd := m.fileTable.Update(msg) + m.fileTable = fileTableModel.(filetable.Model) + return m, tea.Batch(transferProgressCmd, fileTableCmd) default: var cmd tea.Cmd @@ -240,18 +253,15 @@ func (m model) View() string { ui.PadText + m.help.View(m.keys) + "\n\n" case showFinished: - indentedWrappedFiles := indent.String(fmt.Sprintf("Received: %s", wordwrap.String(ui.ItalicText(ui.TopLevelFilesText(m.receivedFiles)), ui.MAX_WIDTH)), ui.MARGIN) - - var oneOrMoreFiles string + oneOrMoreFiles := "object" if len(m.receivedFiles) > 1 { - oneOrMoreFiles = "objects" - } else { - oneOrMoreFiles = "object" + oneOrMoreFiles += "s" } - finishedText := fmt.Sprintf("Received %d %s (%s compressed)\n\n%s", len(m.receivedFiles), oneOrMoreFiles, ui.ByteCountSI(m.payloadSize), indentedWrappedFiles) + finishedText := fmt.Sprintf("Received %d %s (%s compressed)", len(m.receivedFiles), oneOrMoreFiles, ui.ByteCountSI(m.payloadSize)) return ui.PadText + ui.LogSeparator(m.width) + ui.PadText + ui.InfoStyle(finishedText) + "\n\n" + - ui.PadText + m.transferProgress.View() + "\n\n" + ui.PadText + m.transferProgress.View() + "\n\n" + + m.fileTable.View() case showError: return ui.ErrorText(m.errorMessage) diff --git a/ui/sender/sender.go b/ui/sender/sender.go index f66eff4..05d0e2c 100644 --- a/ui/sender/sender.go +++ b/ui/sender/sender.go @@ -105,6 +105,8 @@ func New(filenames []string, addr string, opts ...Option) *tea.Program { keys: ui.Keys, copyMessageTimer: timer.NewWithInterval(ui.TEMP_UI_MESSAGE_DURATION, 100*time.Millisecond), } + m.keys.FileListUp.SetEnabled(true) + m.keys.FileListDown.SetEnabled(true) for _, opt := range opts { opt(&m) } @@ -236,6 +238,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { time.Since(m.transferProgress.TransferStartTime).Round(time.Millisecond).String(), ui.ByteCountSI(m.transferProgress.TransferSpeedEstimateBps), ) + + m.fileTable = m.fileTable.Finalize().(filetable.Model) return m, ui.TaskCmd(message, ui.QuitCmd()) case ui.ErrorMsg: @@ -267,7 +271,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.width = msg.Width transferProgressModel, transferProgressCmd := m.transferProgress.Update(msg) m.transferProgress = transferProgressModel.(transferprogress.Model) - fileTableModel, fileTableCmd := m.fileTable.Update(msg) m.fileTable = fileTableModel.(filetable.Model) return m, tea.Batch(transferProgressCmd, fileTableCmd) @@ -326,13 +329,15 @@ func (m model) View() string { return ui.PadText + ui.LogSeparator(m.width) + ui.PadText + ui.InfoStyle(statusText) + "\n\n" + ui.PadText + m.transferProgress.View() + "\n\n" + + m.fileTable.View() + ui.PadText + m.help.View(m.keys) + "\n\n" case showFinished: finishedText := fmt.Sprintf("Sent %d object(s) (%s compressed)", len(m.fileNames), ui.ByteCountSI(m.payloadSize)) return ui.PadText + ui.LogSeparator(m.width) + ui.PadText + ui.InfoStyle(finishedText) + "\n\n" + - ui.PadText + m.transferProgress.View() + "\n\n" + ui.PadText + m.transferProgress.View() + "\n\n" + + m.fileTable.View() case showError: return ui.ErrorText(m.errorMessage) diff --git a/ui/transferprogress/transferprogress.go b/ui/transferprogress/transferprogress.go index f22ae57..75994e9 100644 --- a/ui/transferprogress/transferprogress.go +++ b/ui/transferprogress/transferprogress.go @@ -3,6 +3,7 @@ package transferprogress import ( "fmt" "math" + "strings" "time" "github.com/SpatiumPortae/portal/ui" @@ -46,12 +47,22 @@ func (Model) Init() tea.Cmd { } func (m Model) View() string { - bytesProgress := fmt.Sprintf("(%s/%s, %s/s)", - ui.ByteCountSI(m.bytesTransferred), ui.ByteCountSI(m.PayloadSize), ui.ByteCountSI(m.TransferSpeedEstimateBps)) - eta := fmt.Sprintf("%v remaining", m.estimatedRemainingDuration.Round(time.Second).String()) + bytesProgress := strings.Builder{} + bytesProgress.WriteRune('(') + bytesProgress.WriteString(fmt.Sprintf("%s/%s", ui.ByteCountSI(m.bytesTransferred), ui.ByteCountSI(m.PayloadSize))) + if m.TransferSpeedEstimateBps > 0 { + bytesProgress.WriteString(fmt.Sprintf(", %s/s", ui.ByteCountSI(m.TransferSpeedEstimateBps))) + } + bytesProgress.WriteRune(')') + + secondsRemaining := m.estimatedRemainingDuration.Round(time.Second) + var eta string + if secondsRemaining > 0 { + eta = fmt.Sprintf("%v remaining", secondsRemaining.String()) + } progressBar := m.progressBar.ViewAs(m.progress) - return bytesProgress + "\t\t" + eta + "\n\n" + + return bytesProgress.String() + "\t\t" + eta + "\n\n" + ui.PadText + progressBar }