Initial Commit

This commit is contained in:
steven carpenter 2025-07-27 15:11:43 -04:00
commit 87a522daab
22 changed files with 1555 additions and 0 deletions

34
tui/model.go Normal file
View file

@ -0,0 +1,34 @@
package tui
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/huh"
)
type viewMode int
const (
monthView viewMode = iota
hourlyView
formView
)
type model struct {
cursorRow int // which row the cursor is sitting on used for cursor highlight
cursorCol int // which col the cursor is sitiing on used for cursor highlight
monthIndex int // what month 1-12 that is being displayed
year int // the current year being displayed
startOffset int // weekday offset where day 1 starts
daysInMonth int // a count of how many days there are in the month to account for 30,31,or 28
mode viewMode
form *huh.Form
// For hourly view
selectedDay int // which day we are looking at in hourly view
hourCursor int // which hour (0-23) is selected in hourly view
}
func (m model) Init() tea.Cmd {
return nil
}

51
tui/newEventForm.go Normal file
View file

@ -0,0 +1,51 @@
package tui
import (
"context"
"strconv"
"git.skdevstudios.com/SK-Development-Studios/go-cal-tui/internal/storage"
"github.com/charmbracelet/huh"
)
func showEventForm(ctx context.Context, onSubmit func(storage.EventInput)) error {
var title, description, year, month, day, startHour, endHour, color string
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().Title("Title").Value(&title),
huh.NewInput().Title("Description").Value(&description),
huh.NewInput().Title("Year").Value(&year),
huh.NewInput().Title("Month").Value(&month),
huh.NewInput().Title("Day").Value(&day),
huh.NewInput().Title("Start Hour (023)").Value(&startHour),
huh.NewInput().Title("End Hour (023)").Value(&endHour),
huh.NewInput().Title("Color (e.g. 99 or #ff5733)").Value(&color),
),
)
if err := form.Run(); err != nil {
return err
}
yr, _ := strconv.Atoi(year)
mo, _ := strconv.Atoi(month)
da, _ := strconv.Atoi(day)
sh, _ := strconv.Atoi(startHour)
eh, _ := strconv.Atoi(endHour)
onSubmit(storage.EventInput{
Title: title,
Description: description,
Year: int32(yr),
Month: int32(mo),
Day: int32(da),
StartHour: int32(sh),
EndHour: int32(eh),
Color: color,
})
return nil
}

46
tui/run.go Normal file
View file

@ -0,0 +1,46 @@
package tui
import (
"fmt"
"time"
"git.skdevstudios.com/SK-Development-Studios/go-cal-tui/internal/storage"
"git.skdevstudios.com/SK-Development-Studios/go-cal-tui/internal/logger"
tea "github.com/charmbracelet/bubbletea"
)
//Table definition constants
const (
numRows = 6
numCols = 7
cellW = 10
cellH = 1
)
func Run() error {
if err := storage.InitDB("data"); err != nil {
logOut := fmt.Errorf("Failed to initalize BadgerDB: %w",err)
logger.Log.Fatal(logOut)
}
defer storage.CloseDB()
// Get the current datetime
now := time.Now()
// Set the start date used for opening the current month instead of 1/1/1970
start := time.Date(now.Year(), now.Month(), 1,0,0,0,0, time.UTC)
// This gets the weekday of the first of the month so that we have the correct day with the correct date
offset := int(start.Weekday())
p := tea.NewProgram(model{
year: now.Year(),
monthIndex: int(now.Month()) - 1,
cursorRow: offset / numCols,
cursorCol: offset % numCols,
startOffset: offset,
mode: monthView,
})
_, err := p.Run()
return err
}

28
tui/styles.go Normal file
View file

@ -0,0 +1,28 @@
package tui
import "github.com/charmbracelet/lipgloss"
// Styles for calendar cells and headers
var (
// this is the style thats used as the default style for the month view
cellStyle = lipgloss.NewStyle().Width(cellW).Height(cellH).Align(lipgloss.Center)
// this is the style that sets the current selected hours bg to orange and text to white
selectedStyle = cellStyle.Copy().Bold(true).Background(lipgloss.Color("12")).Foreground(lipgloss.Color("15"))
// this is the style that sets the color of cells that are not in use for a given month
unselectedStyle = cellStyle.Copy().Background(lipgloss.Color("236")).Foreground(lipgloss.Color("250"))
// this is the style that sets the color of todays cell to purple bg white text
todayStyle = cellStyle.Copy().Background(lipgloss.Color("99")).Foreground(lipgloss.Color("15")).Bold(true)
// this is the style that defines the header where the current months name goes
headerStyle = lipgloss.NewStyle().Width(numCols * cellW).Align(lipgloss.Center).Bold(true)
// this is the style that defines the DoW header where mon-sun go
daysOfWeekStyle = lipgloss.NewStyle().Width(cellW).Align(lipgloss.Center).Bold(true)
// Styles for hourly view cells
// this is the style thats used as the default for the hours view
hourCellStyle = lipgloss.NewStyle().Width(cellW * 3).Height(cellH).Align(lipgloss.Left).PaddingLeft(1)
// This is the style that sets the current selected hours style to orange bg white text
hourSelectedStyle = hourCellStyle.Copy().Bold(true).Background(lipgloss.Color("12")).Foreground(lipgloss.Color("15"))
// this is the style that sets the current hour to purple bg and white text
currentHourStyle = hourCellStyle.Copy().Background(lipgloss.Color("99")).Foreground(lipgloss.Color("15")).Bold(true)
)

109
tui/update.go Normal file
View file

@ -0,0 +1,109 @@
package tui
import (
"context"
"time"
"git.skdevstudios.com/SK-Development-Studios/go-cal-tui/internal/storage"
tea "github.com/charmbracelet/bubbletea"
)
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch m.mode {
case monthView:
return m.updateMonthView(msg)
case hourlyView:
return m.updateHourlyView(msg)
default:
return m, nil
}
}
func (m model) updateMonthView(msg tea.Msg) (tea.Model, tea.Cmd) {
firstDay := time.Date(m.year, time.Month(m.monthIndex+1), 1, 0, 0, 0, 0, time.UTC)
m.startOffset = int(firstDay.Weekday())
m.daysInMonth = time.Date(m.year, time.Month(m.monthIndex+2), 0, 0, 0, 0, 0, time.UTC).Day()
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "a": // Previous month
if m.monthIndex == 0 {
m.monthIndex = 11
m.year--
} else {
m.monthIndex--
}
case "d": // Next month
if m.monthIndex == 11 {
m.monthIndex = 0
m.year++
} else {
m.monthIndex++
}
case "up":
if m.cursorRow > 0 {
m.cursorRow--
}
case "down":
if m.cursorRow < numRows-1 {
m.cursorRow++
}
case "left":
if m.cursorCol > 0 {
m.cursorCol--
}
case "right":
if m.cursorCol < numCols-1 {
m.cursorCol++
}
case "enter":
dayIndex := m.cursorRow*numCols + m.cursorCol
if dayIndex >= m.startOffset && dayIndex < m.startOffset+m.daysInMonth {
m.selectedDay = dayIndex - m.startOffset + 1
m.hourCursor = 0
m.mode = hourlyView
}
}
// Clamp cursor to valid days
dayIndex := m.cursorRow*numCols + m.cursorCol
if dayIndex < m.startOffset || dayIndex >= m.startOffset+m.daysInMonth {
m.cursorRow = m.startOffset / numCols
m.cursorCol = m.startOffset % numCols
}
return m, nil
default:
return m, nil
}
}
func (m model) updateHourlyView(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "n":
showEventForm(context.Background(), func(e storage.EventInput) {
_ = storage.SaveEvent(e)
})
case "esc":
m.mode = monthView
return m, nil
case "ctrl+c", "q":
return m, tea.Quit
case "up":
if m.hourCursor > 0 {
m.hourCursor--
}
case "down":
if m.hourCursor < 23 {
m.hourCursor++
}
}
}
return m, nil
}

0
tui/view.go Normal file
View file