Add mood tracking persistence, keyboard navigation, and yearly mood summary to YearMood widget
This commit is contained in:
parent
2f918332a3
commit
f05e7395f3
@ -100,15 +100,15 @@ func NewNewsText(opts ...text.Option) (*NewsText, error) {
|
||||
return &NewsText{Text: t}, nil
|
||||
}
|
||||
|
||||
func (t *NewsText) Keyboard(k *terminalapi.Keyboard, _ *widgetapi.EventMeta) error {
|
||||
func (widget *NewsText) Keyboard(k *terminalapi.Keyboard, _ *widgetapi.EventMeta) error {
|
||||
if k.Key == keyboard.KeyArrowLeft {
|
||||
if t.Selected == 0 {
|
||||
t.Selected = len(t.newsArticles.Articles) - 1
|
||||
if widget.Selected == 0 {
|
||||
widget.Selected = len(widget.newsArticles.Articles) - 1
|
||||
} else {
|
||||
t.Selected = t.Selected - 1
|
||||
widget.Selected = widget.Selected - 1
|
||||
}
|
||||
} else if k.Key == keyboard.KeyArrowRight {
|
||||
t.Selected = (t.Selected + 1) % len(t.newsArticles.Articles)
|
||||
widget.Selected = (widget.Selected + 1) % len(widget.newsArticles.Articles)
|
||||
}
|
||||
return t.drawNews()
|
||||
return widget.drawNews()
|
||||
}
|
||||
|
||||
@ -3,15 +3,22 @@ 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 {
|
||||
}
|
||||
|
||||
@ -20,97 +27,302 @@ func YearMood() YearMoodOptions {
|
||||
return YearMoodOptions{}
|
||||
}
|
||||
|
||||
var days = map[int]map[int]map[int]int{
|
||||
2025: {
|
||||
1: {
|
||||
1: -1,
|
||||
2: 1,
|
||||
3: 3,
|
||||
4: 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func createYearMood(ctx context.Context, _ terminalapi.Terminal, _ interface{}) widgetapi.Widget {
|
||||
widget := util.PanicOnErrorWithResult(text.New())
|
||||
var selectedMonth = time.Now().Month()
|
||||
var selectedDay = time.Now().Day()
|
||||
widget := util.PanicOnErrorWithResult(NewMoodText())
|
||||
|
||||
widget.selectedMonth = time.Now().Month()
|
||||
widget.selectedDay = time.Now().Day()
|
||||
|
||||
go util.Periodic(ctx, 1*time.Hour, func() error {
|
||||
widget.Reset()
|
||||
|
||||
if err := widget.Write(" Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec\n\n", text.WriteCellOpts(cell.FgColor(cell.ColorWhite))); 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[time.Now().Year()][month+1][day+1]
|
||||
if mood == -1 {
|
||||
cellOptions = append(cellOptions, cell.BgColor(cell.ColorRed))
|
||||
} else if mood == 1 {
|
||||
cellOptions = append(cellOptions, cell.BgColor(cell.ColorPurple))
|
||||
} else if mood == 2 {
|
||||
cellOptions = append(cellOptions, cell.BgColor(cell.ColorYellow))
|
||||
} else if mood == 3 {
|
||||
cellOptions = append(cellOptions, cell.BgColor(cell.ColorGreen))
|
||||
} else {
|
||||
cellOptions = append(cellOptions, cell.BgColor(cell.ColorGray))
|
||||
}
|
||||
|
||||
if day+1 == selectedDay && time.Month(month+1) == 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("\nKeys:\nd=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
|
||||
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)
|
||||
break
|
||||
case '1':
|
||||
widget.WriteMood(1)
|
||||
break
|
||||
case '2':
|
||||
widget.WriteMood(2)
|
||||
break
|
||||
case '3':
|
||||
widget.WriteMood(3)
|
||||
break
|
||||
default:
|
||||
widget.WriteMood(-1)
|
||||
break
|
||||
}
|
||||
return widget.drawTable()
|
||||
}
|
||||
|
||||
func (widget *MoodText) WriteMood(mood int) {
|
||||
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 {
|
||||
widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
}
|
||||
if err := os.WriteFile(moodsFile, newMoods, 0644); err != nil {
|
||||
widget.Write(fmt.Sprintf("Error: %s", err.Error()), text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user