this repo has no description
at main 4.7 kB view raw
1package main 2 3import ( 4 "database/sql" 5 "encoding/base64" 6 "fmt" 7 "log" 8 "os" 9 10 mast "mast/db" 11 parser "mast/parser" 12 13 "github.com/charmbracelet/bubbles/textinput" 14 tea "github.com/charmbracelet/bubbletea" 15 uuid "github.com/gofrs/uuid" 16 "github.com/gorilla/websocket" 17 sqlite3 "github.com/mattn/go-sqlite3" 18) 19 20type model struct { 21 db *sql.DB 22 conn *websocket.Conn 23 todos []string 24 cursor int 25 textInput textinput.Model 26 inputting bool 27} 28 29func initialModel() model { 30 sql.Register("sqlite3_with_extensions", 31 &sqlite3.SQLiteDriver{ 32 Extensions: []string{ 33 "./db/crsqlite.so", 34 }, 35 }) 36 db, err := sql.Open("sqlite3_with_extensions", "db.sqlite") 37 if err != nil { 38 panic(err) 39 } 40 41 err = mast.RunMigrations(db) 42 if err != nil { 43 panic(err) 44 } 45 46 ti := textinput.New() 47 ti.Placeholder = "Add a new todo" 48 ti.Focus() 49 50 conn, _, err := websocket.DefaultDialer.Dial("ws://localhost:8080/sync", nil) 51 if err != nil { 52 log.Println("Error connecting to WebSocket:", err) 53 panic(err) 54 } 55 56 m := model{ 57 db: db, 58 conn: conn, 59 todos: []string{}, 60 cursor: 0, 61 textInput: ti, 62 inputting: false, 63 } 64 m.loadTodos() 65 return m 66} 67 68type CRSQLChange struct { 69 TableName string 70 PK string 71 ColumnName string 72 Value string 73 ColVersion int64 74 DBVersion int64 75 SiteID string 76 CL int64 77 Seq int64 78} 79 80func (m *model) addTodo(todo string) { 81 id, err := uuid.NewV7() 82 if err != nil { 83 panic("Error generating new UUID") 84 } 85 _, err = m.db.Exec(` 86 INSERT INTO todos (id, content) 87 VALUES (?, ?) 88 `, id.String(), todo) 89 if err != nil { 90 panic(err) 91 } 92 93 // TODO: 94 // func sync_changes 95 rows, err := m.db.Query("SELECT * FROM crsql_changes") 96 if err != nil { 97 log.Println("Error querying crsql_changes:", err) 98 return 99 } 100 defer rows.Close() 101 102 var changes []CRSQLChange 103 for rows.Next() { 104 var change CRSQLChange 105 var pkRaw []byte 106 var valueRaw []byte 107 var siteIDRaw []byte 108 err := rows.Scan(&change.TableName, &pkRaw, &change.ColumnName, &valueRaw, 109 &change.ColVersion, &change.DBVersion, &siteIDRaw, &change.CL, &change.Seq) 110 111 if err != nil { 112 log.Println("Error scanning change:", err) 113 return 114 } 115 change.PK = base64.StdEncoding.EncodeToString(pkRaw) 116 change.Value = base64.StdEncoding.EncodeToString(valueRaw) 117 change.SiteID = base64.StdEncoding.EncodeToString(siteIDRaw) 118 119 changes = append(changes, change) 120 fmt.Println(change) 121 } 122 123 if len(changes) > 0 { 124 m.sendToWebSocket(changes) 125 } 126 127 m.loadTodos() 128} 129 130func (m *model) sendToWebSocket(changes []CRSQLChange) { 131 message := struct { 132 Type string `json:"type"` 133 Data []CRSQLChange `json:"data"` 134 }{ 135 Type: "changes", 136 Data: changes, 137 } 138 139 err := m.conn.WriteJSON(message) 140 if err != nil { 141 log.Println("Error sending changes to WebSocket:", err) 142 panic(err) 143 } 144} 145 146func (m *model) loadTodos() { 147 rows, err := m.db.Query("SELECT content FROM todos") 148 if err != nil { 149 panic(err) 150 } 151 defer rows.Close() 152 153 m.todos = []string{} 154 for rows.Next() { 155 var content string 156 if err := rows.Scan(&content); err != nil { 157 panic(err) 158 } 159 m.todos = append(m.todos, content) 160 } 161} 162 163func (m model) Init() tea.Cmd { 164 return nil 165} 166 167func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 168 var cmd tea.Cmd 169 170 switch msg := msg.(type) { 171 case tea.KeyMsg: 172 switch msg.String() { 173 case "ctrl+c", "q": 174 return m, m.quit() 175 case "j", "down": 176 m.cursor = (m.cursor + 1) % len(m.todos) 177 case "k", "up": 178 m.cursor = (m.cursor - 1 + len(m.todos)) % len(m.todos) 179 case "a": 180 if !m.inputting { 181 m.inputting = true 182 return m, textinput.Blink 183 } 184 case "enter": 185 if m.inputting { 186 m.addTodo(m.textInput.Value()) 187 m.textInput.Reset() 188 m.inputting = false 189 } 190 } 191 } 192 193 if m.inputting { 194 m.textInput, cmd = m.textInput.Update(msg) 195 return m, cmd 196 } 197 198 return m, nil 199} 200 201func (m model) quit() tea.Cmd { 202 m.db.Close() 203 m.conn.Close() 204 return tea.Quit 205} 206 207func (m model) View() string { 208 s := "Todo List:\n\n" 209 for i, todo := range m.todos { 210 cursor := " " 211 if m.cursor == i { 212 cursor = ">" 213 } 214 s += fmt.Sprintf("%s %s\n", cursor, todo) 215 } 216 s += "\nPress 'a' to add a new todo, 'q' to quit.\n" 217 218 if m.inputting { 219 s += "\n" + m.textInput.View() 220 } 221 222 return s 223} 224 225// TODO: 226// Remove the parser test and call it when adding a todo 227func main() { 228 if len(os.Args) < 2 { 229 fmt.Println("Please provide a Taskwarrior add command to parse") 230 return 231 } 232 233 input := os.Args[1] 234 result, err := parser.Parse("", []byte(input)) 235 if err != nil { 236 fmt.Printf("Error parsing input: %v\n", err) 237 return 238 } 239 240 fmt.Printf("Parsed result: %+v\n", result) 241 // p := tea.NewProgram(initialModel()) 242 // if _, err := p.Run(); err != nil { 243 // fmt.Printf("Alas, there's been an error: %v", err) 244 // os.Exit(1) 245 // } 246}