123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- // localizing all of the functions required to construct the user interface
- package itashi
- import (
- "bufio"
- "bytes"
- "fmt"
- "log"
- "math"
- "os"
- "strconv"
- "text/template"
- "time"
- tea "github.com/charmbracelet/bubbletea"
- )
- const SPRING_EQUINOX = 81
- const SUMMER_SOLSTICE = 173
- const AUTUMN_EQUINOX = 265
- const WINTER_SOLSTICE = 356
- var Quarters = []int{
- SPRING_EQUINOX,
- SUMMER_SOLSTICE,
- AUTUMN_EQUINOX,
- WINTER_SOLSTICE,
- }
- const HEADER_TEMPLATE = `
- {{.Date}} {{.Season}}, {{.DaysToQuarter}} days until the next {{.QuarterType}}.
- {{.DayOfWeek}}
- {{.Time}} {{.Meridiem}} ({{.TtEod.Hours}}H, {{.TtEod.Minutes}}M -> EoD, {{.TtSun.Hours}}H, {{.TtSun.Minutes}}M -> {{.SunCycle}})
- `
- const TASK_ITEM = `
- +------------------------------------
- |
- | Title: {{.Title}}
- | {{.Desc}}
- | Due: {{.Due}}
- | Priority: {{.Priority}}
- | Done?: {{.Priority}}
- |
- +---------------------------
- `
- const TIME_TO_TEMPLATE = `{{.Hours}}H, {{.Minutes}}M`
- // TODO: put all templates in their own file
- type HeaderData struct {
- Date string
- Season string
- DaysToQuarter int
- QuarterType string
- DayOfWeek string
- Time string
- Meridiem string
- TtEod TimeToSunShift
- TtSun TimeToSunShift
- SunCycle string
- }
- type TimeToSunShift struct {
- Hours int
- Minutes int
- }
- type UserDetails interface {
- daysToQuarter(day int) int
- getQuarterType(day int) string
- getSeason(day int) string
- getTime(ts time.Time) string
- getDate(ts time.Time) string
- getMeridiem(ts time.Time) string
- getTimeToEod(ts time.Time) TimeToSunShift
- getTimeToSunShift(ts time.Time) TimeToSunShift
- getSunCycle(ts time.Time) string
- }
- type UserImplementation struct{}
- func (u UserImplementation) daysToQuarter(day int) int {
- season := u.getSeason(day)
- if season == "Spring" {
- return SUMMER_SOLSTICE - day
- }
- return 1
- }
- /*
- Return the quarter (solstice/equinox). We have to remember that we are returning
- the NEXT season type, i.e. if its currently spring, the next quarter (summer) will have a solstice
- :param day: the numerical day of the year
- :returns: either solstice, or equinox
- */
- func (u UserImplementation) getQuarterType(day int) string {
- season := u.getSeason(day)
- if season == "Winter" {
- return "Equinox"
- }
- if season == "Summer" {
- return "Equinox"
- }
- return "Solstice"
- }
- func (u UserImplementation) getSeason(day int) string {
- if day > 365 {
- return "[REDACTED]"
- }
- if day < 0 {
- return "[REDACTED]"
- }
- if day > 0 && day < SPRING_EQUINOX {
- return "Winter"
- }
- if day > SPRING_EQUINOX && day < SUMMER_SOLSTICE {
- return "Spring"
- }
- if day > SUMMER_SOLSTICE && day < AUTUMN_EQUINOX {
- return "Summer"
- }
- if day > AUTUMN_EQUINOX && day < WINTER_SOLSTICE {
- return "Autumn"
- }
- if day > WINTER_SOLSTICE && day < 365 {
- return "Winter"
- }
- return "idk bruh"
- }
- func (u UserImplementation) getMeridiem(ts time.Time) string {
- if ts.Hour() < 12 {
- return "AM"
- }
- if ts.Hour() >= 12 {
- return "PM"
- }
- return "idk bruh"
- }
- func (u UserImplementation) getTimeToEod(ts time.Time) TimeToSunShift {
- if ts.Hour() > 17 {
- return TimeToSunShift{Hours: 0, Minutes: 0}
- }
- out := time.Date(ts.Year(), ts.Month(), ts.Day(), 17, 0, ts.Second(), ts.Nanosecond(), ts.Location())
- dur := time.Until(out)
- hours := dur.Minutes() / 60
- hours = math.Floor(hours)
- minutes := dur.Minutes() - (hours * 60)
- return TimeToSunShift{Hours: int(hours), Minutes: int(minutes)}
- }
- func (u UserImplementation) getTimeToSunShift(ts time.Time) TimeToSunShift {
- return TimeToSunShift{}
- }
- func (u UserImplementation) getSunCycle(ts time.Time) string {
- return "☼"
- }
- func (u UserImplementation) getTime(ts time.Time) string {
- var hour int
- if ts.Hour() == 0 {
- hour = 12
- } else {
- hour = ts.Hour()
- }
- return fmt.Sprintf("%v:%v", hour, ts.Minute())
- }
- func (u UserImplementation) getDate(ts time.Time) string {
- return fmt.Sprintf("%s %v, %v", ts.Month().String(), ts.Day(), ts.Year())
- }
- // Format the header string with a template
- func getHeader(ud UserDetails, tmpl *template.Template) string {
- rn := time.Now()
- header := HeaderData{
- Date: ud.getDate(rn),
- Season: ud.getSeason(rn.YearDay()),
- DaysToQuarter: ud.daysToQuarter(rn.YearDay()),
- QuarterType: ud.getQuarterType(rn.YearDay()),
- DayOfWeek: rn.Weekday().String(),
- Time: ud.getTime(rn),
- Meridiem: ud.getMeridiem(rn),
- TtEod: ud.getTimeToEod(rn),
- TtSun: ud.getTimeToSunShift(rn),
- SunCycle: ud.getSunCycle(rn),
- }
- var bw bytes.Buffer
- err := tmpl.Execute(&bw, header)
- if err != nil {
- log.Fatal("There was an issue parsing the header. sorry, ", err)
- }
- return bw.String()
- }
- type model struct {
- choices []string
- cursor int
- selected map[int]struct{}
- }
- func InitialModel() model {
- shelf := NewFilesystemShelf(FS_SAVE_LOCATION)
- return model{
- choices: GetTaskNames(shelf.GetAll()),
- selected: make(map[int]struct{}),
- }
- }
- func (m model) Init() tea.Cmd {
- // Just return `nil`, which means "no I/O right now, please."
- return nil
- }
- func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
- switch msg := msg.(type) {
- // Is it a key press?
- case tea.KeyMsg:
- // Cool, what was the actual key pressed?
- switch msg.String() {
- // These keys should exit the program.
- case "ctrl+c", "q":
- return m, tea.Quit
- // The "up" and "k" keys move the cursor up
- case "up", "k":
- if m.cursor > 0 {
- m.cursor--
- }
- // The "down" and "j" keys move the cursor down
- case "down", "j":
- if m.cursor < len(m.choices)-1 {
- m.cursor++
- }
- // The "enter" key and the spacebar (a literal space) toggle
- // the selected state for the item that the cursor is pointing at.
- case "enter", " ":
- _, ok := m.selected[m.cursor]
- if ok {
- delete(m.selected, m.cursor)
- } else {
- m.selected[m.cursor] = struct{}{}
- }
- }
- }
- // Return the updated model to the Bubble Tea runtime for processing.
- // Note that we're not returning a command.
- return m, nil
- }
- func (m model) View() string {
- // The header
- tmpl, err := template.New("header").Parse(HEADER_TEMPLATE)
- if err != nil {
- log.Fatal("Couldnt parse the header template.. sorry. ", err)
- }
- shelf := NewFilesystemShelf(FS_SAVE_LOCATION)
- s := getHeader(UserImplementation{}, tmpl)
- // Iterate over our choices
- for i, choice := range m.choices {
- // Is the cursor pointing at this choice?
- cursor := " " // no cursor
- if m.cursor == i {
- cursor = ">" // cursor!
- }
- // Is this choice selected?
- var taskrender string
- checked := " " // not selected
- if _, ok := m.selected[i]; ok {
- for x := range shelf.Tasks {
- if shelf.Tasks[x].Title == choice {
- taskrender = shelf.RenderTask(shelf.Tasks[x])
- }
- }
- checked = "x" // selected!
- }
- // Render the row
- s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice)
- s += taskrender
- }
- // The footer
- s += "\nPress q to quit.\n"
- // Send the UI for rendering
- return s
- }
- /*
- Add task to the shelf
- */
- func AddTaskPrompt(shelf TaskShelf) {
- task := &Task{}
- var reader *bufio.Reader
- reader = bufio.NewReader(os.Stdout)
- fmt.Print("Enter Task Title: ")
- task.Title, _ = reader.ReadString('\n')
- fmt.Print("Task description: ")
- task.Desc, _ = reader.ReadString('\n')
- fmt.Print("Priority: ")
- priority, _ := reader.ReadString('\n')
- pri, err := strconv.Atoi(priority)
- if err != nil {
- fmt.Print("non-real number sry\n")
- }
- task.Priority = pri
- shelf.AddTask(*task)
- }
|