userinterface.go 7.2 KB

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