Skip to content

Commit

Permalink
Merge pull request #34 from SpatiumPortae/f/filetable
Browse files Browse the repository at this point in the history
feat: file summary table
  • Loading branch information
mellonnen committed Feb 15, 2023
2 parents 445cf30 + d093763 commit 50ee5df
Show file tree
Hide file tree
Showing 7 changed files with 289 additions and 79 deletions.
22 changes: 9 additions & 13 deletions internal/file/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,22 +121,18 @@ func DecompressAndUnarchiveBytes(reader io.Reader) ([]string, int64, error) {
return createdFiles, decompressedSize, nil
}

// Traverses files and directories (recursively) for total size in bytes
func FilesTotalSize(files []*os.File) (int64, error) {
// Traverses a file or directory recursively for total size in bytes.
func FileSize(filePath string) (int64, error) {
var size int64
for _, file := range files {
err := filepath.Walk(file.Name(), func(_ string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
size += info.Size()
}
return err
})
err := filepath.Walk(filePath, func(_ string, info os.FileInfo, err error) error {
if err != nil {
return 0, err
return err
}
size += info.Size()
return err
})
if err != nil {
return 0, err
}
return size, nil
}
Expand Down
62 changes: 37 additions & 25 deletions ui/constants.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package ui

import (
"fmt"
"strings"
"time"

Expand All @@ -11,12 +10,14 @@ import (
)

const (
PADDING = 2
MARGIN = 2
PADDING = 1
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"
Expand All @@ -27,52 +28,63 @@ const (
type KeyMap struct {
Quit key.Binding
CopyPassword key.Binding
FileListUp key.Binding
FileListDown key.Binding
}

func (k KeyMap) ShortHelp() []key.Binding {
return []key.Binding{
k.Quit,
k.CopyPassword,
k.FileListUp,
k.FileListDown,
}
}

func (k KeyMap) FullHelp() [][]key.Binding {
return [][]key.Binding{
{k.Quit, k.CopyPassword},
{k.Quit, k.CopyPassword, k.FileListUp, k.FileListDown},
}
}

func NewProgressBar() progress.Model {
p := progress.New(progress.WithGradient(SECONDARY_ELEMENT_COLOR, ELEMENT_COLOR))
p.PercentFormat = " %.2f%%"
return p
}

var Keys = KeyMap{
Quit: key.NewBinding(
key.WithKeys("q", "esc", "ctrl+c"),
key.WithHelp("(q)", "quit"),
),
CopyPassword: key.NewBinding(
key.WithKeys("c"),
key.WithHelp("(c)", "copy password to clipboard"),
key.WithHelp("(c)", CopyKeyHelpText),
key.WithDisabled(),
),
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(),
),
}

var QuitKeys = []string{"ctrl+c", "q", "esc"}
var PadText = strings.Repeat(" ", PADDING)
var QuitCommandsHelpText = HelpStyle(fmt.Sprintf("(any of [%s] to abort)", strings.Join(QuitKeys, ", ")))

func NewProgressBar() progress.Model {
p := progress.New(progress.WithGradient(SECONDARY_ELEMENT_COLOR, ELEMENT_COLOR))
p.PercentFormat = " %.2f%%"
return p
}

var baseStyle = lipgloss.NewStyle()
var PadText = strings.Repeat(" ", MARGIN)
var BaseStyle = lipgloss.NewStyle()

var InfoStyle = baseStyle.Copy().Foreground(lipgloss.Color(PRIMARY_COLOR)).Render
var HelpStyle = baseStyle.Copy().Foreground(lipgloss.Color(SECONDARY_COLOR)).Render
var ItalicText = baseStyle.Copy().Italic(true).Render
var BoldText = baseStyle.Copy().Bold(true).Render
var ErrorText = baseStyle.Copy().Foreground(lipgloss.Color(ERROR_COLOR)).Render
var WarningText = baseStyle.Copy().Foreground(lipgloss.Color(WARNING_COLOR)).Render
var CheckText = baseStyle.Copy().Foreground(lipgloss.Color(CHECK_COLOR)).Render
var InfoStyle = BaseStyle.Copy().Foreground(lipgloss.Color(PRIMARY_COLOR)).Render
var HelpStyle = BaseStyle.Copy().Foreground(lipgloss.Color(SECONDARY_COLOR)).Render
var ItalicText = BaseStyle.Copy().Italic(true).Render
var BoldText = BaseStyle.Copy().Bold(true).Render
var ErrorText = BaseStyle.Copy().Foreground(lipgloss.Color(ERROR_COLOR)).Render
var WarningText = BaseStyle.Copy().Foreground(lipgloss.Color(WARNING_COLOR)).Render
var CheckText = BaseStyle.Copy().Foreground(lipgloss.Color(CHECK_COLOR)).Render

var CopyKeyHelpText = baseStyle.Render("copy password to clipboard")
var CopyKeyActiveHelpText = CheckText("✓") + HelpStyle(" copied password to clipboard")
var CopyKeyHelpText = BaseStyle.Render("password clipboard")
var CopyKeyActiveHelpText = CheckText("✓") + HelpStyle(" password clipboard")
166 changes: 166 additions & 0 deletions ui/filetable/filetable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package filetable

import (
"math"

"github.com/SpatiumPortae/portal/internal/file"
"github.com/SpatiumPortae/portal/ui"
"github.com/charmbracelet/bubbles/table"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/mattn/go-runewidth"
)

const (
defaultMaxTableHeight = 4
nameColumnWidthFactor float64 = 0.8
sizeColumnWidthFactor float64 = 1 - nameColumnWidthFactor
)

var fileTableStyle = ui.BaseStyle.Copy().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color(ui.SECONDARY_COLOR)).
MarginLeft(ui.MARGIN)

type Option func(m *Model)

type fileRow struct {
path string
formattedSize string
}

type Model struct {
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(defaultMaxTableHeight),
),
}

s := table.DefaultStyles()
s.Header = s.Header.
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color(ui.SECONDARY_COLOR)).
BorderBottom(true).
Bold(true)
s.Selected = s.Selected.
Foreground(lipgloss.Color(ui.DARK_COLOR)).
Background(lipgloss.Color(ui.SECONDARY_ELEMENT_COLOR)).
Bold(false)
m.tableStyles = s
m.table.SetStyles(m.tableStyles)

m.updateColumns()
for _, opt := range opts {
opt(&m)
}

return m
}

func (m *Model) SetFiles(filePaths []string) {
for _, filePath := range filePaths {
size, err := file.FileSize(filePath)
var formattedSize string
if err != nil {
formattedSize = "N/A"
} else {
formattedSize = ui.ByteCountSI(size)
}
m.rows = append(m.rows, fileRow{path: filePath, formattedSize: formattedSize})
}
m.table.SetHeight(int(math.Min(float64(m.MaxHeight), float64(len(filePaths)))))
m.updateColumns()
m.updateRows()
}

func WithFiles(filePaths []string) Option {
return func(m *Model) {
m.SetFiles(filePaths)
}
}

func (m *Model) SetMaxHeight(height int) {
m.MaxHeight = height
}

func WithMaxHeight(height int) Option {
return func(m *Model) {
m.SetMaxHeight(height)
m.updateRows()
}
}

func (m *Model) getMaxWidth() int {
return int(math.Min(ui.MAX_WIDTH-2*ui.MARGIN, float64(m.Width)))
}

func (m *Model) updateColumns() {
w := m.getMaxWidth()
m.table.SetColumns([]table.Column{
{Title: "File", Width: int(float64(w) * nameColumnWidthFactor)},
{Title: "Size", Width: int(float64(w) * sizeColumnWidthFactor)},
})
}

func (m *Model) updateRows() {
var tableRows []table.Row
maxFilePathWidth := int(float64(m.getMaxWidth()) * nameColumnWidthFactor)
for _, row := range m.rows {
path := row.path
// truncate overflowing file paths from the left
if len(path) > maxFilePathWidth {
overflowingLength := len(path) - maxFilePathWidth
path = runewidth.TruncateLeft(path, overflowingLength+1, "…")
}
tableRows = append(tableRows, table.Row{path, row.formattedSize})
}
m.table.SetRows(tableRows)
}

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

switch msg := msg.(type) {

case tea.WindowSizeMsg:
m.Width = msg.Width - 2*ui.MARGIN - 4
if m.Width > ui.MAX_WIDTH {
m.Width = ui.MAX_WIDTH
}
m.updateColumns()
m.updateRows()
return m, nil

}

m.table, cmd = m.table.Update(msg)
return m, cmd
}

func (m Model) View() string {
return fileTableStyle.Render(m.table.View()) + "\n\n"
}
Loading

0 comments on commit 50ee5df

Please sign in to comment.