package main import ( "encoding/json" "fmt" "math/rand" "os" "path" "strings" "time" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" ) const ( signupDir = "/town/signups" acceptedDir = "/town/signups/accepted" rejectedDir = "/town/signups/rejected" ) func getTitle() string { titles := []string{ "yo bum rush the show", "can i kick it?", "super nintendo sega genesis", "birthdays was the worst days", "where were you when we were getting high?", "it's real time, real time, time to get real", } return titles[rand.Intn(len(titles))] } type townSignup struct { When time.Time DecisionTime time.Time Decision string Filename string Answers map[string]string } func (s townSignup) Accept() error { return s.review("accept") } func (s townSignup) Reject() error { return s.review("reject") } func (s townSignup) review(decision string) error { s.DecisionTime = time.Now() s.Decision = "decision" oldpath := path.Join(signupDir, s.Filename) newpath := path.Join(acceptedDir, s.Filename) if decision == "reject" { newpath = path.Join(rejectedDir, s.Filename) } return os.Rename(oldpath, newpath) } func (s townSignup) Render() string { out := fmt.Sprintf("[-:-:b]submitted:[-:-:-] %s\n", s.When.Format("2006-01-02 15:04")) for k, v := range s.Answers { out += fmt.Sprintf("[-:-:b]%s[-:-:-]\n", k) out += strings.TrimSpace(v) out += "\n\n" } return out } func getSignups() ([]townSignup, error) { entries, err := os.ReadDir(signupDir) if err != nil { return nil, fmt.Errorf("failed to read '%s': %w", signupDir, err) } out := []townSignup{} for _, entry := range entries { if !strings.HasSuffix(entry.Name(), "json") { continue } abs := path.Join(signupDir, entry.Name()) data, err := os.ReadFile(abs) if err != nil { return nil, fmt.Errorf("could not read signup file '%s': %w", abs, err) } fmt.Println(string(data)) var signup townSignup err = json.Unmarshal(data, &signup) if err != nil { return nil, fmt.Errorf("could not unmarshal signup file '%s': %w", abs, err) } signup.Filename = entry.Name() out = append(out, signup) } return out, nil } func _main() error { rand.Seed(time.Now().Unix()) signups, err := getSignups() if err != nil { return fmt.Errorf("could not get signups: %w", err) } signupIx := 0 pages := tview.NewPages() mainFlex := tview.NewFlex() title := tview.NewTextView() title.SetText(getTitle()) title.SetTextAlign(tview.AlignCenter) title.SetTextColor(tcell.ColorPurple) title.SetBackgroundColor(tcell.ColorBlack) appView := tview.NewTextView() appView.SetDynamicColors(true) if len(signups) == 0 { appView.SetText("no signups found.") } else { appView.SetText(signups[signupIx].Render()) } legend := tview.NewTextView() legend.SetText("s: skip r: random A: approve R: reject N: notate Q: quit") legend.SetTextColor(tcell.ColorPurple) legend.SetTextAlign(tview.AlignCenter) legend.SetBackgroundColor(tcell.ColorBlack) mainFlex.SetDirection(tview.FlexRow) mainFlex.AddItem(title, 1, -1, false) mainFlex.AddItem(appView, 0, 1, true) mainFlex.AddItem(legend, 1, -1, false) pages.AddPage("main", mainFlex, true, true) // TODO page for textarea to notate app := tview.NewApplication() app.SetRoot(pages, true) // TODO count of pending signups somewhere app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { switch event.Rune() { case 's': signupIx++ if signupIx == len(signups) { signupIx = 0 } appView.SetText(signups[signupIx].Render()) case 'r': signupIx = rand.Intn(len(signups)) appView.SetText(signups[signupIx].Render()) case 'A': err := signups[signupIx].Accept() if err != nil { // TODO report in app panic(fmt.Errorf("failed to approve '%s': %w", signups[signupIx].Filename, err)) } // TODO remove from signups list case 'R': err = signups[signupIx].Reject() if err != nil { // TODO report in app panic(fmt.Errorf("failed to rejec t'%s': %w", signups[signupIx].Filename, err)) } // TODO remove from signups list case 'n': // TODO notate case 'Q': app.Stop() } return event }) return app.Run() } func main() { err := _main() if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }