package widgets import ( "ArinDash/util" "context" "encoding/json" "fmt" "os" "strconv" "strings" "time" "github.com/mum4k/termdash/cell" "github.com/mum4k/termdash/keyboard" "github.com/mum4k/termdash/terminal/terminalapi" "github.com/mum4k/termdash/widgetapi" "github.com/mum4k/termdash/widgets/text" ) const moodsFile = "moods.json" type YearMoodOptions struct { } func YearMood() YearMoodOptions { widgetOptions["YearMoodOptions"] = createYearMood return YearMoodOptions{} } func createYearMood(ctx context.Context, _ terminalapi.Terminal, _ interface{}) widgetapi.Widget { widget := util.PanicOnErrorWithResult(NewMoodText()) go util.Periodic(ctx, 1*time.Hour, func() error { widget.selectedMonth = time.Now().Month() widget.selectedDay = time.Now().Day() return widget.drawTable() }) return widget } func (widget *MoodText) drawTable() error { days := moods() widget.Reset() if err := widget.Write(" "); err != nil { return err } for m := range 12 { mood := moodToColor(widget.moodMonth(m + 1)) if err := widget.Write(fmt.Sprintf("%2s", time.Month(m + 1).String()[0:3]), text.WriteCellOpts(cell.BgColor(mood), cell.FgColor(cell.ColorWhite))); err != nil { return err } if m < 11 { if err := widget.Write(" "); err != nil { return err } } else { if err := widget.Write("\n"); err != nil { return err } } } for day := range 31 { for month := range 12 { date := time.Date(time.Now().Year(), time.Month(month+1), day+1, 0, 0, 0, 0, time.UTC) if date.Month() != time.Month(month+1) { if err := widget.Write(" "); err != nil { return err } continue } cellOptions := make([]cell.Option, 0) weekday := time.Date(time.Now().Year(), time.Month(month+1), day+1, 0, 0, 0, 0, time.UTC).Weekday() if weekday == time.Saturday || weekday == time.Sunday { cellOptions = append(cellOptions, cell.Dim()) } cellOptions = append(cellOptions, cell.FgColor(cell.ColorWhite)) cellOptions = append(cellOptions, cell.Bold()) mood := days[strconv.Itoa(time.Now().Year())][strconv.Itoa(month+1)][strconv.Itoa(day+1)] cellOptions = append(cellOptions, cell.BgColor(moodToColor(mood))) if day+1 == widget.selectedDay && time.Month(month+1) == widget.selectedMonth { if err := widget.Write(">", text.WriteCellOpts(append(cellOptions, cell.Blink())...)); err != nil { return err } if err := widget.Write(fmt.Sprintf("%2d", day+1), text.WriteCellOpts(cellOptions...)); err != nil { return err } } else { if err := widget.Write(fmt.Sprintf(" %2d", day+1), text.WriteCellOpts(cellOptions...)); err != nil { return err } } if time.Now().Day() == day+1 && time.Now().Month() == time.Month(month+1) { if err := widget.Write("<", text.WriteCellOpts(cellOptions...)); err != nil { return err } } else { if err := widget.Write(" ", text.WriteCellOpts(cellOptions...)); err != nil { return err } } if month+1 < 12 { if err := widget.Write(" "); err != nil { return err } } } if err := widget.Write("\n"); err != nil { return err } } if err := widget.Write("\n"); err != nil { return err } for k, v := range totalMoodPercents() { if v == 0 { continue } amount := int(59 * v) if amount == 0 { amount = 1 } if err := widget.Write(strings.Repeat("█", amount), text.WriteCellOpts(cell.BgColor(cell.ColorGray), cell.FgColor(moodToColor(k)))); err != nil { return err } } if err := widget.Write("\n\n"); err != nil { return err } if err := widget.Write("<-=delete 1=idk(purple) 2=meh(yellow) 3=great(green) other=bad(red)", text.WriteCellOpts(cell.FgColor(cell.ColorGray))); err != nil { return err } return nil } func moods() map[string]map[string]map[string]int { moods := make(map[string]map[string]map[string]int) file, err := os.ReadFile(moodsFile) if err == nil { if err := json.Unmarshal(file, &moods); err != nil { panic(err) } } else { initMoods, _ := json.Marshal(moods) if err := os.WriteFile(moodsFile, initMoods, 0644); err != nil { // ignore } } return moods } func moodToColor(mood int) cell.Color { switch mood { case 1: return cell.ColorPurple case 2: return cell.ColorYellow case 3: return cell.ColorGreen case -1: return cell.ColorRed default: return cell.ColorGray } } func totalMoodPercents() map[int]float64 { m := moods() now := time.Now() currentYear := strconv.Itoa(now.Year()) // Initialize percentages for all known moods percents := map[int]float64{ 1: 0.0, // idk 2: 0.0, // meh 3: 0.0, // great -1: 0.0, // bad 0: 0.0, // no mood recorded } // Calculate total days in the current year firstDay := time.Date(now.Year(), time.January, 1, 0, 0, 0, 0, time.UTC) nextYear := time.Date(now.Year()+1, time.January, 1, 0, 0, 0, 0, time.UTC) totalDaysInYear := int(nextYear.Sub(firstDay).Hours() / 24) yearData, ok := m[currentYear] if !ok { percents[0] = 1.0 return percents } counts := make(map[int]int) recordedDaysCount := 0 for _, monthData := range yearData { for _, mood := range monthData { if mood == 0 { continue } counts[mood]++ recordedDaysCount++ } } // Any day that doesn't have a recorded mood (1, 2, 3, or -1) is counted as 0 counts[0] = totalDaysInYear - recordedDaysCount for mood := range percents { if count, exists := counts[mood]; exists { percents[mood] = float64(count) / float64(totalDaysInYear) } } return percents } func (widget *MoodText) moodMonth(month int) int { days := moods()[strconv.Itoa(time.Now().Year())][strconv.Itoa(month)] counts := make(map[int]int) mostFrequentMood := 0 maxCount := 0 for _, mood := range days { if mood == 0 { continue } counts[mood]++ if counts[mood] > maxCount { maxCount = counts[mood] mostFrequentMood = mood } } return mostFrequentMood } type MoodText struct { *text.Text selectedMonth time.Month selectedDay int } func NewMoodText(opts ...text.Option) (*MoodText, error) { t, err := text.New(opts...) if err != nil { return nil, err } return &MoodText{Text: t}, nil } func (widget *MoodText) Keyboard(k *terminalapi.Keyboard, _ *widgetapi.EventMeta) error { switch k.Key { case keyboard.KeyEsc, keyboard.KeyCtrlC: break case keyboard.KeyArrowUp: if widget.selectedDay == 1 { widget.selectedDay = 31 } else { widget.selectedDay-- } break case keyboard.KeyArrowDown: if widget.selectedDay == 31 { widget.selectedDay = 1 } else { widget.selectedDay++ } break case keyboard.KeyArrowLeft: if widget.selectedMonth == 1 { widget.selectedMonth = 12 } else { widget.selectedMonth-- } break case keyboard.KeyArrowRight: if widget.selectedMonth == 12 { widget.selectedMonth = 1 } else { widget.selectedMonth++ } break case keyboard.KeyBackspace, keyboard.KeyBackspace2: widget.WriteMood(0) case '1': widget.WriteMood(1) case '2': widget.WriteMood(2) case '3': widget.WriteMood(3) ChangeMood("happy") default: widget.WriteMood(-1) ChangeMood("cry") } return widget.drawTable() } func (widget *MoodText) WriteMood(mood int) error { m := moods() if m[strconv.Itoa(time.Now().Year())] == nil { m[strconv.Itoa(time.Now().Year())] = make(map[string]map[string]int) } if m[strconv.Itoa(time.Now().Year())][strconv.Itoa(int(widget.selectedMonth))] == nil { m[strconv.Itoa(time.Now().Year())][strconv.Itoa(int(widget.selectedMonth))] = make(map[string]int) } m[strconv.Itoa(time.Now().Year())][strconv.Itoa(int(widget.selectedMonth))][strconv.Itoa(widget.selectedDay)] = mood newMoods, err := json.MarshalIndent(m, "", " ") if err != nil { return widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed))) } if err := os.WriteFile(moodsFile, newMoods, 0644); err != nil { return widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed))) } return nil }