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
|
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 k.Key == keyboard.KeyArrowLeft {
|
||||||
if t.Selected == 0 {
|
if widget.Selected == 0 {
|
||||||
t.Selected = len(t.newsArticles.Articles) - 1
|
widget.Selected = len(widget.newsArticles.Articles) - 1
|
||||||
} else {
|
} else {
|
||||||
t.Selected = t.Selected - 1
|
widget.Selected = widget.Selected - 1
|
||||||
}
|
}
|
||||||
} else if k.Key == keyboard.KeyArrowRight {
|
} 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 (
|
import (
|
||||||
"ArinDash/util"
|
"ArinDash/util"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mum4k/termdash/cell"
|
"github.com/mum4k/termdash/cell"
|
||||||
|
"github.com/mum4k/termdash/keyboard"
|
||||||
"github.com/mum4k/termdash/terminal/terminalapi"
|
"github.com/mum4k/termdash/terminal/terminalapi"
|
||||||
"github.com/mum4k/termdash/widgetapi"
|
"github.com/mum4k/termdash/widgetapi"
|
||||||
"github.com/mum4k/termdash/widgets/text"
|
"github.com/mum4k/termdash/widgets/text"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const moodsFile = "moods.json"
|
||||||
|
|
||||||
type YearMoodOptions struct {
|
type YearMoodOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,97 +27,302 @@ func YearMood() YearMoodOptions {
|
|||||||
return 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 {
|
func createYearMood(ctx context.Context, _ terminalapi.Terminal, _ interface{}) widgetapi.Widget {
|
||||||
widget := util.PanicOnErrorWithResult(text.New())
|
widget := util.PanicOnErrorWithResult(NewMoodText())
|
||||||
var selectedMonth = time.Now().Month()
|
|
||||||
var selectedDay = time.Now().Day()
|
widget.selectedMonth = time.Now().Month()
|
||||||
|
widget.selectedDay = time.Now().Day()
|
||||||
|
|
||||||
go util.Periodic(ctx, 1*time.Hour, func() error {
|
go util.Periodic(ctx, 1*time.Hour, func() error {
|
||||||
widget.Reset()
|
return widget.drawTable()
|
||||||
|
|
||||||
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
|
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