Fix table formatting
This commit is contained in:
330
common/styles.go
330
common/styles.go
@ -1,25 +1,29 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"math"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
// "github.com/charmbracelet/bubbles/table"
|
// "github.com/charmbracelet/bubbles/table"
|
||||||
|
|
||||||
"github.com/charmbracelet/huh"
|
"github.com/charmbracelet/huh"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
|
||||||
"tasksquire/components/table"
|
|
||||||
"tasksquire/taskwarrior"
|
"tasksquire/taskwarrior"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TableStyle struct {
|
||||||
|
Header lipgloss.Style
|
||||||
|
Cell lipgloss.Style
|
||||||
|
Selected lipgloss.Style
|
||||||
|
}
|
||||||
|
|
||||||
type Styles struct {
|
type Styles struct {
|
||||||
Main lipgloss.Style
|
Base lipgloss.Style
|
||||||
|
|
||||||
Form *huh.Theme
|
Form *huh.Theme
|
||||||
TableStyle table.Styles
|
TableStyle TableStyle
|
||||||
|
|
||||||
Active lipgloss.Style
|
Active lipgloss.Style
|
||||||
Alternate lipgloss.Style
|
Alternate lipgloss.Style
|
||||||
@ -72,13 +76,13 @@ type Styles struct {
|
|||||||
|
|
||||||
func NewStyles(config *taskwarrior.TWConfig) *Styles {
|
func NewStyles(config *taskwarrior.TWConfig) *Styles {
|
||||||
styles := parseColors(config.GetConfig())
|
styles := parseColors(config.GetConfig())
|
||||||
styles.Main = lipgloss.NewStyle()
|
styles.Base = lipgloss.NewStyle()
|
||||||
|
|
||||||
styles.TableStyle = table.Styles{
|
styles.TableStyle = TableStyle{
|
||||||
// Header: lipgloss.NewStyle().Bold(true).Padding(0, 1).BorderBottom(true),
|
// Header: lipgloss.NewStyle().Bold(true).Padding(0, 1).BorderBottom(true),
|
||||||
Cell: lipgloss.NewStyle().Padding(0, 1, 0, 0),
|
Cell: lipgloss.NewStyle().Padding(0, 1, 0, 0),
|
||||||
Header: lipgloss.NewStyle().Bold(true).Padding(0, 1, 0, 0).Underline(true),
|
Header: lipgloss.NewStyle().Bold(true).Padding(0, 1, 0, 0).Underline(true),
|
||||||
Selected: lipgloss.NewStyle().Foreground(styles.Active.GetForeground()).Background(styles.Active.GetBackground()).Bold(true).Reverse(true),
|
Selected: lipgloss.NewStyle().Bold(true).Reverse(true),
|
||||||
}
|
}
|
||||||
|
|
||||||
formTheme := huh.ThemeBase()
|
formTheme := huh.ThemeBase()
|
||||||
@ -104,104 +108,102 @@ func parseColors(config map[string]string) *Styles {
|
|||||||
|
|
||||||
for key, value := range config {
|
for key, value := range config {
|
||||||
if strings.HasPrefix(key, "color.") {
|
if strings.HasPrefix(key, "color.") {
|
||||||
if value != "" {
|
_, colorValue, _ := strings.Cut(key, ".")
|
||||||
color := strings.Split(key, ".")[1]
|
switch colorValue {
|
||||||
switch color {
|
case "active":
|
||||||
case "active":
|
styles.Active = parseColorString(value)
|
||||||
styles.Active = parseColorString(value)
|
case "alternate":
|
||||||
case "alternate":
|
styles.Alternate = parseColorString(value)
|
||||||
styles.Alternate = parseColorString(value)
|
case "blocked":
|
||||||
case "blocked":
|
styles.Blocked = parseColorString(value)
|
||||||
styles.Blocked = parseColorString(value)
|
case "blocking":
|
||||||
case "blocking":
|
styles.Blocking = parseColorString(value)
|
||||||
styles.Blocking = parseColorString(value)
|
case "burndown.done":
|
||||||
case "burndown.done":
|
styles.BurndownDone = parseColorString(value)
|
||||||
styles.BurndownDone = parseColorString(value)
|
case "burndown.pending":
|
||||||
case "burndown.pending":
|
styles.BurndownPending = parseColorString(value)
|
||||||
styles.BurndownPending = parseColorString(value)
|
case "burndown.started":
|
||||||
case "burndown.started":
|
styles.BurndownStarted = parseColorString(value)
|
||||||
styles.BurndownStarted = parseColorString(value)
|
case "calendar.due":
|
||||||
case "calendar.due":
|
styles.CalendarDue = parseColorString(value)
|
||||||
styles.CalendarDue = parseColorString(value)
|
case "calendar.due.today":
|
||||||
case "calendar.due.today":
|
styles.CalendarDueToday = parseColorString(value)
|
||||||
styles.CalendarDueToday = parseColorString(value)
|
case "calendar.holiday":
|
||||||
case "calendar.holiday":
|
styles.CalendarHoliday = parseColorString(value)
|
||||||
styles.CalendarHoliday = parseColorString(value)
|
case "calendar.overdue":
|
||||||
case "calendar.overdue":
|
styles.CalendarOverdue = parseColorString(value)
|
||||||
styles.CalendarOverdue = parseColorString(value)
|
case "calendar.scheduled":
|
||||||
case "calendar.scheduled":
|
styles.CalendarScheduled = parseColorString(value)
|
||||||
styles.CalendarScheduled = parseColorString(value)
|
case "calendar.today":
|
||||||
case "calendar.today":
|
styles.CalendarToday = parseColorString(value)
|
||||||
styles.CalendarToday = parseColorString(value)
|
case "calendar.weekend":
|
||||||
case "calendar.weekend":
|
styles.CalendarWeekend = parseColorString(value)
|
||||||
styles.CalendarWeekend = parseColorString(value)
|
case "calendar.weeknumber":
|
||||||
case "calendar.weeknumber":
|
styles.CalendarWeeknumber = parseColorString(value)
|
||||||
styles.CalendarWeeknumber = parseColorString(value)
|
case "completed":
|
||||||
case "completed":
|
styles.Completed = parseColorString(value)
|
||||||
styles.Completed = parseColorString(value)
|
case "debug":
|
||||||
case "debug":
|
styles.Debug = parseColorString(value)
|
||||||
styles.Debug = parseColorString(value)
|
case "deleted":
|
||||||
case "deleted":
|
styles.Deleted = parseColorString(value)
|
||||||
styles.Deleted = parseColorString(value)
|
case "due":
|
||||||
case "due":
|
styles.Due = parseColorString(value)
|
||||||
styles.Due = parseColorString(value)
|
case "due.today":
|
||||||
case "due.today":
|
styles.DueToday = parseColorString(value)
|
||||||
styles.DueToday = parseColorString(value)
|
case "error":
|
||||||
case "error":
|
styles.Error = parseColorString(value)
|
||||||
styles.Error = parseColorString(value)
|
case "footnote":
|
||||||
case "footnote":
|
styles.Footnote = parseColorString(value)
|
||||||
styles.Footnote = parseColorString(value)
|
case "header":
|
||||||
case "header":
|
styles.Header = parseColorString(value)
|
||||||
styles.Header = parseColorString(value)
|
case "history.add":
|
||||||
case "history.add":
|
styles.HistoryAdd = parseColorString(value)
|
||||||
styles.HistoryAdd = parseColorString(value)
|
case "history.delete":
|
||||||
case "history.delete":
|
styles.HistoryDelete = parseColorString(value)
|
||||||
styles.HistoryDelete = parseColorString(value)
|
case "history.done":
|
||||||
case "history.done":
|
styles.HistoryDone = parseColorString(value)
|
||||||
styles.HistoryDone = parseColorString(value)
|
case "label":
|
||||||
case "label":
|
styles.Label = parseColorString(value)
|
||||||
styles.Label = parseColorString(value)
|
case "label.sort":
|
||||||
case "label.sort":
|
styles.LabelSort = parseColorString(value)
|
||||||
styles.LabelSort = parseColorString(value)
|
case "overdue":
|
||||||
case "overdue":
|
styles.Overdue = parseColorString(value)
|
||||||
styles.Overdue = parseColorString(value)
|
case "project.none":
|
||||||
case "project.none":
|
styles.ProjectNone = parseColorString(value)
|
||||||
styles.ProjectNone = parseColorString(value)
|
case "recurring":
|
||||||
case "recurring":
|
styles.Recurring = parseColorString(value)
|
||||||
styles.Recurring = parseColorString(value)
|
case "scheduled":
|
||||||
case "scheduled":
|
styles.Scheduled = parseColorString(value)
|
||||||
styles.Scheduled = parseColorString(value)
|
case "summary.background":
|
||||||
case "summary.background":
|
styles.SummaryBackground = parseColorString(value)
|
||||||
styles.SummaryBackground = parseColorString(value)
|
case "summary.bar":
|
||||||
case "summary.bar":
|
styles.SummaryBar = parseColorString(value)
|
||||||
styles.SummaryBar = parseColorString(value)
|
case "sync.added":
|
||||||
case "sync.added":
|
styles.SyncAdded = parseColorString(value)
|
||||||
styles.SyncAdded = parseColorString(value)
|
case "sync.changed":
|
||||||
case "sync.changed":
|
styles.SyncChanged = parseColorString(value)
|
||||||
styles.SyncChanged = parseColorString(value)
|
case "sync.rejected":
|
||||||
case "sync.rejected":
|
styles.SyncRejected = parseColorString(value)
|
||||||
styles.SyncRejected = parseColorString(value)
|
case "tag.next":
|
||||||
case "tag.next":
|
styles.TagNext = parseColorString(value)
|
||||||
styles.TagNext = parseColorString(value)
|
case "tag.none":
|
||||||
case "tag.none":
|
styles.TagNone = parseColorString(value)
|
||||||
styles.TagNone = parseColorString(value)
|
case "tagged":
|
||||||
case "tagged":
|
styles.Tagged = parseColorString(value)
|
||||||
styles.Tagged = parseColorString(value)
|
case "uda.priority.H":
|
||||||
case "uda.priority.H":
|
styles.UdaPriorityH = parseColorString(value)
|
||||||
styles.UdaPriorityH = parseColorString(value)
|
case "uda.priority.L":
|
||||||
case "uda.priority.L":
|
styles.UdaPriorityL = parseColorString(value)
|
||||||
styles.UdaPriorityL = parseColorString(value)
|
case "uda.priority.M":
|
||||||
case "uda.priority.M":
|
styles.UdaPriorityM = parseColorString(value)
|
||||||
styles.UdaPriorityM = parseColorString(value)
|
case "undo.after":
|
||||||
case "undo.after":
|
styles.UndoAfter = parseColorString(value)
|
||||||
styles.UndoAfter = parseColorString(value)
|
case "undo.before":
|
||||||
case "undo.before":
|
styles.UndoBefore = parseColorString(value)
|
||||||
styles.UndoBefore = parseColorString(value)
|
case "until":
|
||||||
case "until":
|
styles.Until = parseColorString(value)
|
||||||
styles.Until = parseColorString(value)
|
case "warning":
|
||||||
case "warning":
|
styles.Warning = parseColorString(value)
|
||||||
styles.Warning = parseColorString(value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -211,6 +213,10 @@ func parseColors(config map[string]string) *Styles {
|
|||||||
|
|
||||||
func parseColorString(color string) lipgloss.Style {
|
func parseColorString(color string) lipgloss.Style {
|
||||||
style := lipgloss.NewStyle()
|
style := lipgloss.NewStyle()
|
||||||
|
if color == "" {
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
|
||||||
if strings.Contains(color, "on") {
|
if strings.Contains(color, "on") {
|
||||||
fgbg := strings.Split(color, "on")
|
fgbg := strings.Split(color, "on")
|
||||||
fg := strings.TrimSpace(fgbg[0])
|
fg := strings.TrimSpace(fgbg[0])
|
||||||
@ -230,12 +236,7 @@ func parseColorString(color string) lipgloss.Style {
|
|||||||
|
|
||||||
func parseColor(color string) lipgloss.Color {
|
func parseColor(color string) lipgloss.Color {
|
||||||
if strings.HasPrefix(color, "rgb") {
|
if strings.HasPrefix(color, "rgb") {
|
||||||
rgb, err := parseRGBString(strings.TrimPrefix(color, "rgb"))
|
return lipgloss.Color(convertRgbToAnsi(strings.TrimPrefix(color, "rgb")))
|
||||||
if err != nil {
|
|
||||||
slog.Error("Invalid RGB color format")
|
|
||||||
return lipgloss.Color("0")
|
|
||||||
}
|
|
||||||
return lipgloss.Color(rgbToAnsi(rgb))
|
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(color, "color") {
|
if strings.HasPrefix(color, "color") {
|
||||||
return lipgloss.Color(strings.TrimPrefix(color, "color"))
|
return lipgloss.Color(strings.TrimPrefix(color, "color"))
|
||||||
@ -256,36 +257,33 @@ func parseColor(color string) lipgloss.Color {
|
|||||||
return lipgloss.Color("0")
|
return lipgloss.Color("0")
|
||||||
}
|
}
|
||||||
|
|
||||||
type RGB struct {
|
func convertRgbToAnsi(rgbString string) string {
|
||||||
r int
|
|
||||||
g int
|
|
||||||
b int
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseRGBString(rgbString string) (RGB, error) {
|
|
||||||
var err error
|
var err error
|
||||||
rgb := RGB{}
|
|
||||||
|
|
||||||
if len(rgbString) != 3 {
|
if len(rgbString) != 3 {
|
||||||
return rgb, errors.New("invalid RGB format")
|
slog.Error("Invalid RGB color format")
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
rgb.r, err = strconv.Atoi(string(rgbString[0]))
|
r, err := strconv.Atoi(string(rgbString[0]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return rgb, errors.New("invalid value for R")
|
slog.Error("Invalid value for R")
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
rgb.g, err = strconv.Atoi(string(rgbString[1]))
|
g, err := strconv.Atoi(string(rgbString[1]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return rgb, errors.New("invalid value for G")
|
slog.Error("Invalid value for G")
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
rgb.b, err = strconv.Atoi(string(rgbString[2]))
|
b, err := strconv.Atoi(string(rgbString[2]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return rgb, errors.New("invalid value for B")
|
slog.Error("Invalid value for B")
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return rgb, nil
|
return strconv.Itoa(16 + (36 * r) + (6 * g) + b)
|
||||||
}
|
}
|
||||||
|
|
||||||
var colorStrings = map[string]int{
|
var colorStrings = map[string]int{
|
||||||
@ -306,79 +304,3 @@ var colorStrings = map[string]int{
|
|||||||
"bright cyan": 14,
|
"bright cyan": 14,
|
||||||
"bright white": 15,
|
"bright white": 15,
|
||||||
}
|
}
|
||||||
|
|
||||||
var baseColors = []RGB{
|
|
||||||
{0, 0, 0}, // Black
|
|
||||||
{128, 0, 0}, // Red
|
|
||||||
{0, 128, 0}, // Green
|
|
||||||
{128, 128, 0}, // Yellow
|
|
||||||
{0, 0, 128}, // Blue
|
|
||||||
{128, 0, 128}, // Magenta
|
|
||||||
{0, 128, 128}, // Cyan
|
|
||||||
{192, 192, 192}, // White
|
|
||||||
}
|
|
||||||
|
|
||||||
var highIntensityColors = []RGB{
|
|
||||||
{128, 128, 128}, // Bright Black (Gray)
|
|
||||||
{255, 0, 0}, // Bright Red
|
|
||||||
{0, 255, 0}, // Bright Green
|
|
||||||
{255, 255, 0}, // Bright Yellow
|
|
||||||
{0, 0, 255}, // Bright Blue
|
|
||||||
{255, 0, 255}, // Bright Magenta
|
|
||||||
{0, 255, 255}, // Bright Cyan
|
|
||||||
{255, 255, 255}, // Bright White
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the Euclidean distance between two colors
|
|
||||||
func colorDistance(c1, c2 RGB) float64 {
|
|
||||||
return math.Sqrt(float64((c1.r-c2.r)*(c1.r-c2.r) + (c1.g-c2.g)*(c1.g-c2.g) + (c1.b-c2.b)*(c1.b-c2.b)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert RGB to the nearest ANSI color code
|
|
||||||
func rgbToAnsi(rgb RGB) string {
|
|
||||||
// Check standard and high-intensity colors
|
|
||||||
allColors := append(baseColors, highIntensityColors...)
|
|
||||||
bestIndex := 0
|
|
||||||
minDist := colorDistance(rgb, allColors[0])
|
|
||||||
|
|
||||||
for i := 1; i < len(allColors); i++ {
|
|
||||||
dist := colorDistance(rgb, allColors[i])
|
|
||||||
if dist < minDist {
|
|
||||||
bestIndex = i
|
|
||||||
minDist = dist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if bestIndex < 8 {
|
|
||||||
return strconv.Itoa(bestIndex)
|
|
||||||
} else if bestIndex < 16 {
|
|
||||||
return strconv.Itoa(bestIndex + 8)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check 6x6x6 color cube
|
|
||||||
for i := 0; i < 216; i++ {
|
|
||||||
cubeColor := RGB{
|
|
||||||
(rgb.r / 51) * 51,
|
|
||||||
(rgb.g / 51) * 51,
|
|
||||||
(rgb.b / 51) * 51,
|
|
||||||
}
|
|
||||||
dist := colorDistance(rgb, cubeColor)
|
|
||||||
if dist < minDist {
|
|
||||||
bestIndex = i + 16
|
|
||||||
minDist = dist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check grayscale colors
|
|
||||||
for i := 0; i < 24; i++ {
|
|
||||||
gray := i*10 + 8
|
|
||||||
grayColor := RGB{gray, gray, gray}
|
|
||||||
dist := colorDistance(rgb, grayColor)
|
|
||||||
if dist < minDist {
|
|
||||||
bestIndex = i + 232
|
|
||||||
minDist = dist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return strconv.Itoa(bestIndex)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package table
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/key"
|
"github.com/charmbracelet/bubbles/key"
|
||||||
"github.com/charmbracelet/bubbles/viewport"
|
"github.com/charmbracelet/bubbles/viewport"
|
||||||
@ -23,7 +24,7 @@ type Model struct {
|
|||||||
rowStyles []lipgloss.Style
|
rowStyles []lipgloss.Style
|
||||||
cursor int
|
cursor int
|
||||||
focus bool
|
focus bool
|
||||||
styles Styles
|
styles common.TableStyle
|
||||||
styleFunc StyleFunc
|
styleFunc StyleFunc
|
||||||
|
|
||||||
viewport viewport.Model
|
viewport viewport.Model
|
||||||
@ -108,25 +109,8 @@ func DefaultKeyMap() KeyMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Styles contains style definitions for this list component. By default, these
|
|
||||||
// values are generated by DefaultStyles.
|
|
||||||
type Styles struct {
|
|
||||||
Header lipgloss.Style
|
|
||||||
Cell lipgloss.Style
|
|
||||||
Selected lipgloss.Style
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultStyles returns a set of default style definitions for this table.
|
|
||||||
func DefaultStyles() Styles {
|
|
||||||
return Styles{
|
|
||||||
Selected: lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("212")),
|
|
||||||
Header: lipgloss.NewStyle().Bold(true).Padding(0, 1),
|
|
||||||
Cell: lipgloss.NewStyle().Padding(0, 1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetStyles sets the table styles.
|
// SetStyles sets the table styles.
|
||||||
func (m *Model) SetStyles(s Styles) {
|
func (m *Model) SetStyles(s common.TableStyle) {
|
||||||
m.styles = s
|
m.styles = s
|
||||||
m.UpdateViewport()
|
m.UpdateViewport()
|
||||||
}
|
}
|
||||||
@ -139,11 +123,11 @@ type Option func(*Model)
|
|||||||
// New creates a new model for the table widget.
|
// New creates a new model for the table widget.
|
||||||
func New(com *common.Common, opts ...Option) Model {
|
func New(com *common.Common, opts ...Option) Model {
|
||||||
m := Model{
|
m := Model{
|
||||||
|
common: com,
|
||||||
cursor: 0,
|
cursor: 0,
|
||||||
viewport: viewport.New(0, 20),
|
viewport: viewport.New(0, 20),
|
||||||
|
|
||||||
KeyMap: DefaultKeyMap(),
|
KeyMap: DefaultKeyMap(),
|
||||||
styles: DefaultStyles(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
@ -158,9 +142,75 @@ func New(com *common.Common, opts ...Option) Model {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) parseRowStyles()
|
// TODO: dynamically read rule.precedence.color
|
||||||
|
func (m *Model) parseRowStyles(rows taskwarrior.Tasks) []lipgloss.Style {
|
||||||
|
styles := make([]lipgloss.Style, len(rows))
|
||||||
|
if len(rows) == 0 {
|
||||||
|
return styles
|
||||||
|
}
|
||||||
|
for i, task := range rows {
|
||||||
|
if task.Status == "deleted" {
|
||||||
|
styles[i] = m.common.Styles.Deleted.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if task.Status == "completed" {
|
||||||
|
styles[i] = m.common.Styles.Completed.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if task.Status == "pending" && task.Start != "" {
|
||||||
|
styles[i] = m.common.Styles.Active.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// TODO: implement keyword
|
||||||
|
// TODO: implement tag
|
||||||
|
// TODO: implement project
|
||||||
|
if !task.GetDate("due").IsZero() && task.GetDate("due").Before(time.Now()) {
|
||||||
|
styles[i] = m.common.Styles.Overdue.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if task.Scheduled != "" {
|
||||||
|
styles[i] = m.common.Styles.Scheduled.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !task.GetDate("due").IsZero() && task.GetDate("due").Truncate(24*time.Hour).Equal(time.Now().Truncate(24*time.Hour)) {
|
||||||
|
styles[i] = m.common.Styles.DueToday.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if task.Due != "" {
|
||||||
|
styles[i] = m.common.Styles.Due.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(task.Depends) > 0 {
|
||||||
|
styles[i] = m.common.Styles.Blocked.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// TODO implement blocking
|
||||||
|
if task.Recur != "" {
|
||||||
|
styles[i] = m.common.Styles.Recurring.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(task.Tags) > 0 {
|
||||||
|
styles[i] = m.common.Styles.Tagged.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||||
|
taskIteration:
|
||||||
|
for _, tag := range task.Tags {
|
||||||
|
if tag == "next" {
|
||||||
|
styles[i] = m.common.Styles.TagNext.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||||
|
break taskIteration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// TODO implement uda
|
||||||
|
styles[i] = m.common.Styles.Base.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||||
|
}
|
||||||
|
return styles
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Model) parseColumns(cols []Column) []Column {
|
func (m *Model) parseColumns(cols []Column) []Column {
|
||||||
|
if len(cols) == 0 {
|
||||||
|
return cols
|
||||||
|
}
|
||||||
|
|
||||||
for i, col := range cols {
|
for i, col := range cols {
|
||||||
for _, task := range m.rows {
|
for _, task := range m.rows {
|
||||||
col.ContentWidth = max(col.ContentWidth, lipgloss.Width(task.GetString(col.Name)))
|
col.ContentWidth = max(col.ContentWidth, lipgloss.Width(task.GetString(col.Name)))
|
||||||
@ -172,9 +222,8 @@ func (m *Model) parseColumns(cols []Column) []Column {
|
|||||||
nonZeroWidths := 0
|
nonZeroWidths := 0
|
||||||
descIndex := -1
|
descIndex := -1
|
||||||
for i, col := range cols {
|
for i, col := range cols {
|
||||||
col.Width = max(col.ContentWidth, lipgloss.Width(col.Title))
|
if col.ContentWidth > 0 {
|
||||||
|
col.Width = max(col.ContentWidth, lipgloss.Width(col.Title))
|
||||||
if col.Width > 0 {
|
|
||||||
nonZeroWidths++
|
nonZeroWidths++
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,7 +300,7 @@ func WithFocused(f bool) Option {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithStyles sets the table styles.
|
// WithStyles sets the table styles.
|
||||||
func WithStyles(s Styles) Option {
|
func WithStyles(s common.TableStyle) Option {
|
||||||
return func(m *Model) {
|
return func(m *Model) {
|
||||||
m.styles = s
|
m.styles = s
|
||||||
}
|
}
|
||||||
@ -497,13 +546,16 @@ func (m *Model) renderRow(r int) string {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var cellStyle lipgloss.Style
|
var cellStyle lipgloss.Style
|
||||||
if m.styleFunc != nil {
|
// if m.styleFunc != nil {
|
||||||
cellStyle = m.styleFunc(r, i, m.rows[r].GetString(col.Name))
|
// cellStyle = m.styleFunc(r, i, m.rows[r].GetString(col.Name))
|
||||||
if r == m.cursor {
|
// if r == m.cursor {
|
||||||
cellStyle.Inherit(m.styles.Selected)
|
// cellStyle.Inherit(m.styles.Selected)
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
cellStyle = m.rowStyle[r]
|
cellStyle = m.rowStyles[r]
|
||||||
|
// }
|
||||||
|
if r == m.cursor {
|
||||||
|
cellStyle = cellStyle.Inherit(m.styles.Selected)
|
||||||
}
|
}
|
||||||
|
|
||||||
style := lipgloss.NewStyle().Width(m.cols[i].Width).MaxWidth(m.cols[i].Width).Inline(true)
|
style := lipgloss.NewStyle().Width(m.cols[i].Width).MaxWidth(m.cols[i].Width).Inline(true)
|
||||||
|
|||||||
@ -96,7 +96,7 @@ func (p *ContextPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *ContextPickerPage) View() string {
|
func (p *ContextPickerPage) View() string {
|
||||||
return p.common.Styles.Main.Render(p.form.View())
|
return p.common.Styles.Base.Render(p.form.View())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ContextPickerPage) updateContextCmd() tea.Msg {
|
func (p *ContextPickerPage) updateContextCmd() tea.Msg {
|
||||||
|
|||||||
@ -92,7 +92,7 @@ func (p *ProjectPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProjectPickerPage) View() string {
|
func (p *ProjectPickerPage) View() string {
|
||||||
return p.common.Styles.Main.Render(p.form.View())
|
return p.common.Styles.Base.Render(p.form.View())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProjectPickerPage) updateProjectCmd() tea.Msg {
|
func (p *ProjectPickerPage) updateProjectCmd() tea.Msg {
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
package pages
|
package pages
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
|
||||||
"tasksquire/common"
|
"tasksquire/common"
|
||||||
"tasksquire/components/table"
|
"tasksquire/components/table"
|
||||||
"tasksquire/taskwarrior"
|
"tasksquire/taskwarrior"
|
||||||
@ -34,15 +33,15 @@ func NewReportPage(com *common.Common, report *taskwarrior.Report) *ReportPage {
|
|||||||
activeReport: report,
|
activeReport: report,
|
||||||
activeContext: com.TW.GetActiveContext(),
|
activeContext: com.TW.GetActiveContext(),
|
||||||
activeProject: "",
|
activeProject: "",
|
||||||
|
taskTable: table.New(com),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReportPage) SetSize(width int, height int) {
|
func (p *ReportPage) SetSize(width int, height int) {
|
||||||
p.common.SetSize(width, height)
|
p.common.SetSize(width, height)
|
||||||
slog.Info("FramSize", "vert", p.common.Styles.Main.GetVerticalFrameSize(), "horz", p.common.Styles.Main.GetHorizontalFrameSize())
|
|
||||||
|
|
||||||
p.taskTable.SetWidth(width - p.common.Styles.Main.GetVerticalFrameSize())
|
p.taskTable.SetWidth(width - p.common.Styles.Base.GetVerticalFrameSize())
|
||||||
p.taskTable.SetHeight(height - p.common.Styles.Main.GetHorizontalFrameSize())
|
p.taskTable.SetHeight(height - p.common.Styles.Base.GetHorizontalFrameSize())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReportPage) Init() tea.Cmd {
|
func (p *ReportPage) Init() tea.Cmd {
|
||||||
@ -130,9 +129,9 @@ func (p *ReportPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
func (p *ReportPage) View() string {
|
func (p *ReportPage) View() string {
|
||||||
// return p.common.Styles.Main.Render(p.taskTable.View()) + "\n"
|
// return p.common.Styles.Main.Render(p.taskTable.View()) + "\n"
|
||||||
if p.tasks == nil || len(p.tasks) == 0 {
|
if p.tasks == nil || len(p.tasks) == 0 {
|
||||||
return p.common.Styles.Main.Render("No tasks found")
|
return p.common.Styles.Base.Render("No tasks found")
|
||||||
}
|
}
|
||||||
return p.common.Styles.Main.Render(p.taskTable.View())
|
return p.taskTable.View()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReportPage) populateTaskTable(tasks taskwarrior.Tasks) {
|
func (p *ReportPage) populateTaskTable(tasks taskwarrior.Tasks) {
|
||||||
@ -151,11 +150,12 @@ func (p *ReportPage) populateTaskTable(tasks taskwarrior.Tasks) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p.taskTable = table.New(
|
p.taskTable = table.New(
|
||||||
|
p.common,
|
||||||
table.WithReport(p.activeReport),
|
table.WithReport(p.activeReport),
|
||||||
table.WithTasks(tasks),
|
table.WithTasks(tasks),
|
||||||
table.WithFocused(true),
|
table.WithFocused(true),
|
||||||
table.WithWidth(p.common.Width()-p.common.Styles.Main.GetVerticalFrameSize()),
|
table.WithWidth(p.common.Width()-p.common.Styles.Base.GetVerticalFrameSize()),
|
||||||
table.WithHeight(p.common.Height()-p.common.Styles.Main.GetHorizontalFrameSize()-10),
|
table.WithHeight(p.common.Height()-p.common.Styles.Base.GetHorizontalFrameSize()-1),
|
||||||
table.WithStyles(p.common.Styles.TableStyle),
|
table.WithStyles(p.common.Styles.TableStyle),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -93,7 +93,7 @@ func (p *ReportPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReportPickerPage) View() string {
|
func (p *ReportPickerPage) View() string {
|
||||||
return p.common.Styles.Main.Render(p.form.View())
|
return p.common.Styles.Base.Render(p.form.View())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReportPickerPage) updateReportCmd() tea.Msg {
|
func (p *ReportPickerPage) updateReportCmd() tea.Msg {
|
||||||
|
|||||||
@ -314,7 +314,7 @@ func (s *StatusLine) View() string {
|
|||||||
var mode string
|
var mode string
|
||||||
switch s.mode {
|
switch s.mode {
|
||||||
case ModeNormal:
|
case ModeNormal:
|
||||||
mode = s.common.Styles.Main.Render("NORMAL")
|
mode = s.common.Styles.Base.Render("NORMAL")
|
||||||
case ModeInsert:
|
case ModeInsert:
|
||||||
mode = s.common.Styles.Active.Inline(true).Render("INSERT")
|
mode = s.common.Styles.Active.Inline(true).Render("INSERT")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dtformat = "20060102T150405Z"
|
||||||
|
)
|
||||||
|
|
||||||
type Annotation struct {
|
type Annotation struct {
|
||||||
Entry string `json:"entry,omitempty"`
|
Entry string `json:"entry,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
@ -184,6 +188,15 @@ func (t *Task) GetString(fieldWFormat string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Task) GetDate(dateString string) time.Time {
|
||||||
|
dt, err := time.Parse(dtformat, dateString)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to parse time:", err)
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
return dt
|
||||||
|
}
|
||||||
|
|
||||||
type Tasks []*Task
|
type Tasks []*Task
|
||||||
|
|
||||||
type Context struct {
|
type Context struct {
|
||||||
@ -212,7 +225,6 @@ func formatDate(date string, format string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
dtformat := "20060102T150405Z"
|
|
||||||
dt, err := time.Parse(dtformat, date)
|
dt, err := time.Parse(dtformat, date)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to parse time:", err)
|
slog.Error("Failed to parse time:", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user