package ui import ( "fmt" "io" "log/slog" "os" gemlog "git.travisshears.com/travisshears/gemlog-cli/gemlog" tea "github.com/charmbracelet/bubbletea" ) type Page string const ( ActionList Page = "actionList" EntryList Page = "entryList" Entry Page = "entry" ) type SwitchPages struct{ Page Page } type Notification string type ErrorMsg struct{ err error } type GemLogsLoaded struct{ Logs []gemlog.GemlogListEntry } type GemLogLoaded struct{ Log gemlog.GemlogEntry } type uiState struct { notification string errorTxt string page Page entryListPage EntryListPageModel entryPage EntryPageModel actionListPage ActionListPageModel } type context struct { config *gemlog.Config } type model struct { ui uiState context *context } func initialModel(config *gemlog.Config) model { return model{ ui: uiState{ page: ActionList, actionListPage: initialActionListPageModel(), entryListPage: initialEntryListPageModel(), entryPage: initialEntryPageModel(), }, context: &context{ config: config, }, } } func (m model) Init() tea.Cmd { cmds := make([]tea.Cmd, 0) cmds = append(cmds, tea.SetWindowTitle("Gemlog CLI")) // TODO: add init commands from other pages return tea.Batch(cmds...) } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds := make([]tea.Cmd, 3) switch msg := msg.(type) { case SwitchPages: m.ui.page = msg.Page case ErrorMsg: m.ui.errorTxt = fmt.Sprintf("%s\n\n", fmt.Errorf("%s", msg)) case Notification: m.ui.notification = fmt.Sprintf("%s\n\n", string(msg)) case tea.KeyMsg: switch msg.String() { case "ctrl+c", "q": return m, tea.Quit } } actionListM, cmd := m.ui.actionListPage.Update(msg, m.ui.page == ActionList, m.context) m.ui.actionListPage = actionListM cmds = append(cmds, cmd) entryListM, cmd := m.ui.entryListPage.Update(msg, m.ui.page == EntryList, m.context) m.ui.entryListPage = entryListM cmds = append(cmds, cmd) entrytM, cmd := m.ui.entryPage.Update(msg, m.ui.page == Entry, m.context) m.ui.entryPage = entrytM cmds = append(cmds, cmd) return m, tea.Batch(cmds...) } func (m model) View() string { if len(m.ui.errorTxt) > 0 { return m.ui.errorTxt } s := "" if m.ui.notification != "" { s += m.ui.notification } switch m.ui.page { case ActionList: s += m.ui.actionListPage.View() case EntryList: s += m.ui.entryListPage.View() case Entry: s += m.ui.entryPage.View() } // s += "\nPress q to quit.\n" return s } var enableLogs bool = false func Run() { if enableLogs { f, err := tea.LogToFile("debug.log", "debug") if err != nil { fmt.Println("fatal:", err) os.Exit(1) } defer f.Close() } else { logger := slog.New(slog.NewJSONHandler(io.Discard, nil)) slog.SetDefault(logger) } slog.Info("Starting gemlog cli") config, err := gemlog.LoadConfig() if err != nil { fmt.Printf("Error loading config: %v", err) os.Exit(1) } err = gemlog.CheckDBConnection(config) if err != nil { fmt.Printf("Error checking db connection: %v", err) os.Exit(1) } p := tea.NewProgram( initialModel(config), tea.WithAltScreen(), // use the full size of the terminal in its "alternate screen buffer" tea.WithMouseCellMotion(), // turn on mouse support so we can track the mouse wheel ) if _, err := p.Run(); err != nil { fmt.Printf("Alas, there's been an error: %v", err) os.Exit(1) } }