userinterface.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. // localizing all of the functions required to construct the user interface
  2. package itashi
  3. import (
  4. "bytes"
  5. "fmt"
  6. "log"
  7. "text/template"
  8. "time"
  9. tea "github.com/charmbracelet/bubbletea"
  10. )
  11. const SPRING_EQUINOX = 81
  12. const SUMMER_SOLSTICE = 173
  13. const AUTUMN_EQUINOX = 265
  14. const WINTER_SOLSTICE = 356
  15. var Quarters = []int{
  16. SPRING_EQUINOX,
  17. SUMMER_SOLSTICE,
  18. AUTUMN_EQUINOX,
  19. WINTER_SOLSTICE,
  20. }
  21. const HEADER_TEMPLATE = `
  22. {{.Date}} {{.Season}}, {{.DaysToQuarter}} days until the next {{.QuarterType}}.
  23. {{.DayOfWeek}}
  24. {{.Time}} {{.Meridiem}} ({{.TtEod.Hours}}H, {{.TtEod.Minutes}}M -> EoD, {{.TtSun.Hours}}H, {{.TtSun.Minutes}}M -> {{.SunCycle}})
  25. `
  26. const TIME_TO_TEMPLATE = `{{.Hours}}H, {{.Minutes}}M`
  27. type HeaderData struct {
  28. Date string
  29. Season string
  30. DaysToQuarter int
  31. QuarterType string
  32. DayOfWeek string
  33. Time string
  34. Meridiem string
  35. TtEod TimeToSunShift
  36. TtSun TimeToSunShift
  37. SunCycle string
  38. }
  39. type TimeToSunShift struct {
  40. Hours int
  41. Minutes int
  42. }
  43. type UserDetails interface {
  44. daysToQuarter(day int) int
  45. getQuarterType(day int) string
  46. getSeason(day int) string
  47. getTime(ts time.Time) string
  48. getDate(ts time.Time) string
  49. getMeridiem(ts time.Time) string
  50. getTimeToEod(ts time.Time) TimeToSunShift
  51. getTimeToSunShift(ts time.Time) TimeToSunShift
  52. getSunCycle(ts time.Time) string
  53. }
  54. type UserImplementation struct{}
  55. func (u UserImplementation) daysToQuarter(day int) int {
  56. season := u.getSeason(day)
  57. if season == "Spring" {
  58. return SUMMER_SOLSTICE - day
  59. }
  60. return 1
  61. }
  62. /*
  63. Return the quarter (solstice/equinox). We have to remember that we are returning
  64. the NEXT season type, i.e. if its currently spring, the next quarter (summer) will have a solstice
  65. :param day: the numerical day of the year
  66. :returns: either solstice, or equinox
  67. */
  68. func (u UserImplementation) getQuarterType(day int) string {
  69. season := u.getSeason(day)
  70. if season == "Winter" {
  71. return "Equinox"
  72. }
  73. if season == "Summer" {
  74. return "Equinox"
  75. }
  76. return "Solstice"
  77. }
  78. func (u UserImplementation) getSeason(day int) string {
  79. if day > 365 {
  80. return "[REDACTED]"
  81. }
  82. if day < 0 {
  83. return "[REDACTED]"
  84. }
  85. if day > 0 && day < SPRING_EQUINOX {
  86. return "Winter"
  87. }
  88. if day > SPRING_EQUINOX && day < SUMMER_SOLSTICE {
  89. return "Spring"
  90. }
  91. if day > SUMMER_SOLSTICE && day < AUTUMN_EQUINOX {
  92. return "Summer"
  93. }
  94. if day > AUTUMN_EQUINOX && day < WINTER_SOLSTICE {
  95. return "Autumn"
  96. }
  97. if day > WINTER_SOLSTICE && day < 365 {
  98. return "Winter"
  99. }
  100. return "idk bruh"
  101. }
  102. func (u UserImplementation) getMeridiem(ts time.Time) string {
  103. if ts.Hour() < 12 {
  104. return "AM"
  105. }
  106. if ts.Hour() >= 12 {
  107. return "PM"
  108. }
  109. return "idk bruh"
  110. }
  111. func (u UserImplementation) getTimeToEod(ts time.Time) TimeToSunShift {
  112. if ts.Hour() > 17 {
  113. return TimeToSunShift{Hours: 0, Minutes: 0}
  114. }
  115. out := time.Date(ts.Year(), ts.Month(), ts.Day(), 17, 0, ts.Second(), ts.Nanosecond(), ts.Location())
  116. dur := time.Until(out)
  117. return TimeToSunShift{Hours: int(dur.Hours()), Minutes: int(dur.Minutes())}
  118. }
  119. func (u UserImplementation) getTimeToSunShift(ts time.Time) TimeToSunShift {
  120. return TimeToSunShift{}
  121. }
  122. func (u UserImplementation) getSunCycle(ts time.Time) string {
  123. return "☼"
  124. }
  125. func (u UserImplementation) getTime(ts time.Time) string {
  126. return fmt.Sprintf("%v:%v", ts.Hour(), ts.Minute())
  127. }
  128. func (u UserImplementation) getDate(ts time.Time) string {
  129. return fmt.Sprintf("%s %v, %v", ts.Month().String(), ts.Day(), ts.Year())
  130. }
  131. // Format the header string with a template
  132. func getHeader(ud UserDetails, tmpl *template.Template) string {
  133. rn := time.Now()
  134. header := HeaderData{
  135. Date: ud.getDate(rn),
  136. Season: ud.getSeason(rn.YearDay()),
  137. DaysToQuarter: ud.daysToQuarter(rn.YearDay()),
  138. QuarterType: ud.getQuarterType(rn.YearDay()),
  139. DayOfWeek: rn.Weekday().String(),
  140. Time: ud.getTime(rn),
  141. Meridiem: ud.getMeridiem(rn),
  142. TtEod: ud.getTimeToEod(rn),
  143. TtSun: ud.getTimeToSunShift(rn),
  144. SunCycle: ud.getSunCycle(rn),
  145. }
  146. var bw bytes.Buffer
  147. err := tmpl.Execute(&bw, header)
  148. if err != nil {
  149. log.Fatal("There was an issue parsing the header. sorry, ", err)
  150. }
  151. return bw.String()
  152. }
  153. type model struct {
  154. choices []string
  155. cursor int
  156. selected map[int]struct{}
  157. }
  158. func InitialModel() model {
  159. shelf := NewFilesystemShelf(FS_SAVE_LOCATION)
  160. // shelf.AddTask(Task{Id: 1, Title: "This is a sample task", Desc: "Quick sample task that im testing the storage with",
  161. // Due: time.Now().AddDate(0, 0, 2), Done: false, Priority: 1})
  162. return model{
  163. // Our to-do list is a grocery list
  164. choices: GetTaskNames(shelf.GetAll()),
  165. // A map which indicates which choices are selected. We're using
  166. // the map like a mathematical set. The keys refer to the indexes
  167. // of the `choices` slice, above.
  168. selected: make(map[int]struct{}),
  169. }
  170. }
  171. func (m model) Init() tea.Cmd {
  172. // Just return `nil`, which means "no I/O right now, please."
  173. return nil
  174. }
  175. func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  176. switch msg := msg.(type) {
  177. // Is it a key press?
  178. case tea.KeyMsg:
  179. // Cool, what was the actual key pressed?
  180. switch msg.String() {
  181. // These keys should exit the program.
  182. case "ctrl+c", "q":
  183. return m, tea.Quit
  184. // The "up" and "k" keys move the cursor up
  185. case "up", "k":
  186. if m.cursor > 0 {
  187. m.cursor--
  188. }
  189. // The "down" and "j" keys move the cursor down
  190. case "down", "j":
  191. if m.cursor < len(m.choices)-1 {
  192. m.cursor++
  193. }
  194. // The "enter" key and the spacebar (a literal space) toggle
  195. // the selected state for the item that the cursor is pointing at.
  196. case "enter", " ":
  197. _, ok := m.selected[m.cursor]
  198. if ok {
  199. delete(m.selected, m.cursor)
  200. } else {
  201. m.selected[m.cursor] = struct{}{}
  202. }
  203. }
  204. }
  205. // Return the updated model to the Bubble Tea runtime for processing.
  206. // Note that we're not returning a command.
  207. return m, nil
  208. }
  209. func (m model) View() string {
  210. // The header
  211. tmpl, err := template.New("header").Parse(HEADER_TEMPLATE)
  212. if err != nil {
  213. log.Fatal("Couldnt parse the header template.. sorry. ", err)
  214. }
  215. s := getHeader(UserImplementation{}, tmpl)
  216. // Iterate over our choices
  217. for i, choice := range m.choices {
  218. // Is the cursor pointing at this choice?
  219. cursor := " " // no cursor
  220. if m.cursor == i {
  221. cursor = ">" // cursor!
  222. }
  223. // Is this choice selected?
  224. checked := " " // not selected
  225. if _, ok := m.selected[i]; ok {
  226. checked = "x" // selected!
  227. }
  228. // Render the row
  229. s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice)
  230. }
  231. // The footer
  232. s += "\nPress q to quit.\n"
  233. // Send the UI for rendering
  234. return s
  235. }