2024-08-03 18:06:43 -07:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2024-08-31 18:06:10 -07:00
|
|
|
"database/sql"
|
|
|
|
"encoding/base64"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"os"
|
2024-08-03 18:06:43 -07:00
|
|
|
|
2024-08-31 18:12:03 -07:00
|
|
|
mast "mast/db"
|
2024-08-31 23:00:05 -07:00
|
|
|
parser "mast/parser"
|
2024-08-31 18:06:10 -07:00
|
|
|
|
|
|
|
"github.com/charmbracelet/bubbles/textinput"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
|
|
uuid "github.com/gofrs/uuid"
|
|
|
|
"github.com/gorilla/websocket"
|
|
|
|
sqlite3 "github.com/mattn/go-sqlite3"
|
2024-08-03 18:06:43 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
type model struct {
|
2024-08-31 18:06:10 -07:00
|
|
|
db *sql.DB
|
|
|
|
conn *websocket.Conn
|
|
|
|
todos []string
|
|
|
|
cursor int
|
|
|
|
textInput textinput.Model
|
|
|
|
inputting bool
|
2024-08-03 18:06:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func initialModel() model {
|
2024-08-31 18:06:10 -07:00
|
|
|
sql.Register("sqlite3_with_extensions",
|
|
|
|
&sqlite3.SQLiteDriver{
|
|
|
|
Extensions: []string{
|
|
|
|
"./db/crsqlite.so",
|
|
|
|
},
|
|
|
|
})
|
|
|
|
db, err := sql.Open("sqlite3_with_extensions", "db.sqlite")
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = mast.RunMigrations(db)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ti := textinput.New()
|
|
|
|
ti.Placeholder = "Add a new todo"
|
|
|
|
ti.Focus()
|
|
|
|
|
|
|
|
conn, _, err := websocket.DefaultDialer.Dial("ws://localhost:8080/sync", nil)
|
|
|
|
if err != nil {
|
|
|
|
log.Println("Error connecting to WebSocket:", err)
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
m := model{
|
|
|
|
db: db,
|
|
|
|
conn: conn,
|
|
|
|
todos: []string{},
|
|
|
|
cursor: 0,
|
|
|
|
textInput: ti,
|
|
|
|
inputting: false,
|
|
|
|
}
|
|
|
|
m.loadTodos()
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
|
|
|
|
type CRSQLChange struct {
|
|
|
|
TableName string
|
|
|
|
PK string
|
|
|
|
ColumnName string
|
|
|
|
Value string
|
|
|
|
ColVersion int64
|
|
|
|
DBVersion int64
|
|
|
|
SiteID string
|
|
|
|
CL int64
|
|
|
|
Seq int64
|
2024-08-03 18:06:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *model) addTodo(todo string) {
|
2024-08-31 18:06:10 -07:00
|
|
|
id, err := uuid.NewV7()
|
|
|
|
if err != nil {
|
|
|
|
panic("Error generating new UUID")
|
|
|
|
}
|
|
|
|
_, err = m.db.Exec(`
|
|
|
|
INSERT INTO todos (id, content)
|
|
|
|
VALUES (?, ?)
|
|
|
|
`, id.String(), todo)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2024-08-31 18:12:03 -07:00
|
|
|
// TODO:
|
|
|
|
// func sync_changes
|
2024-08-31 18:06:10 -07:00
|
|
|
rows, err := m.db.Query("SELECT * FROM crsql_changes")
|
|
|
|
if err != nil {
|
|
|
|
log.Println("Error querying crsql_changes:", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
var changes []CRSQLChange
|
|
|
|
for rows.Next() {
|
|
|
|
var change CRSQLChange
|
|
|
|
var pkRaw []byte
|
|
|
|
var valueRaw []byte
|
|
|
|
var siteIDRaw []byte
|
|
|
|
err := rows.Scan(&change.TableName, &pkRaw, &change.ColumnName, &valueRaw,
|
|
|
|
&change.ColVersion, &change.DBVersion, &siteIDRaw, &change.CL, &change.Seq)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.Println("Error scanning change:", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
change.PK = base64.StdEncoding.EncodeToString(pkRaw)
|
|
|
|
change.Value = base64.StdEncoding.EncodeToString(valueRaw)
|
|
|
|
change.SiteID = base64.StdEncoding.EncodeToString(siteIDRaw)
|
|
|
|
|
|
|
|
changes = append(changes, change)
|
|
|
|
fmt.Println(change)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(changes) > 0 {
|
|
|
|
m.sendToWebSocket(changes)
|
|
|
|
}
|
|
|
|
|
|
|
|
m.loadTodos()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *model) sendToWebSocket(changes []CRSQLChange) {
|
|
|
|
message := struct {
|
|
|
|
Type string `json:"type"`
|
|
|
|
Data []CRSQLChange `json:"data"`
|
|
|
|
}{
|
|
|
|
Type: "changes",
|
|
|
|
Data: changes,
|
|
|
|
}
|
|
|
|
|
|
|
|
err := m.conn.WriteJSON(message)
|
|
|
|
if err != nil {
|
|
|
|
log.Println("Error sending changes to WebSocket:", err)
|
|
|
|
panic(err)
|
|
|
|
}
|
2024-08-03 18:06:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *model) loadTodos() {
|
2024-08-31 18:06:10 -07:00
|
|
|
rows, err := m.db.Query("SELECT content FROM todos")
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
m.todos = []string{}
|
|
|
|
for rows.Next() {
|
|
|
|
var content string
|
|
|
|
if err := rows.Scan(&content); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
m.todos = append(m.todos, content)
|
|
|
|
}
|
2024-08-03 18:06:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m model) Init() tea.Cmd {
|
2024-08-31 18:06:10 -07:00
|
|
|
return nil
|
2024-08-03 18:06:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
2024-08-31 18:06:10 -07:00
|
|
|
var cmd tea.Cmd
|
|
|
|
|
|
|
|
switch msg := msg.(type) {
|
|
|
|
case tea.KeyMsg:
|
|
|
|
switch msg.String() {
|
|
|
|
case "ctrl+c", "q":
|
|
|
|
return m, m.quit()
|
|
|
|
case "j", "down":
|
|
|
|
m.cursor = (m.cursor + 1) % len(m.todos)
|
|
|
|
case "k", "up":
|
|
|
|
m.cursor = (m.cursor - 1 + len(m.todos)) % len(m.todos)
|
|
|
|
case "a":
|
|
|
|
if !m.inputting {
|
|
|
|
m.inputting = true
|
|
|
|
return m, textinput.Blink
|
|
|
|
}
|
|
|
|
case "enter":
|
|
|
|
if m.inputting {
|
|
|
|
m.addTodo(m.textInput.Value())
|
|
|
|
m.textInput.Reset()
|
|
|
|
m.inputting = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if m.inputting {
|
|
|
|
m.textInput, cmd = m.textInput.Update(msg)
|
|
|
|
return m, cmd
|
|
|
|
}
|
|
|
|
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m model) quit() tea.Cmd {
|
|
|
|
m.db.Close()
|
|
|
|
m.conn.Close()
|
|
|
|
return tea.Quit
|
2024-08-03 18:06:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m model) View() string {
|
2024-08-31 18:06:10 -07:00
|
|
|
s := "Todo List:\n\n"
|
|
|
|
for i, todo := range m.todos {
|
|
|
|
cursor := " "
|
|
|
|
if m.cursor == i {
|
|
|
|
cursor = ">"
|
|
|
|
}
|
|
|
|
s += fmt.Sprintf("%s %s\n", cursor, todo)
|
|
|
|
}
|
|
|
|
s += "\nPress 'a' to add a new todo, 'q' to quit.\n"
|
|
|
|
|
|
|
|
if m.inputting {
|
|
|
|
s += "\n" + m.textInput.View()
|
|
|
|
}
|
|
|
|
|
|
|
|
return s
|
2024-08-03 18:06:43 -07:00
|
|
|
}
|
|
|
|
|
2024-08-31 23:00:05 -07:00
|
|
|
// TODO:
|
|
|
|
// Remove the parser test and call it when adding a todo
|
2024-08-03 18:06:43 -07:00
|
|
|
func main() {
|
2024-08-31 23:00:05 -07:00
|
|
|
if len(os.Args) < 2 {
|
|
|
|
fmt.Println("Please provide a Taskwarrior add command to parse")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
input := os.Args[1]
|
|
|
|
result, err := parser.Parse("", []byte(input))
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Error parsing input: %v\n", err)
|
|
|
|
return
|
2024-08-31 18:06:10 -07:00
|
|
|
}
|
2024-08-31 23:00:05 -07:00
|
|
|
|
|
|
|
fmt.Printf("Parsed result: %+v\n", result)
|
|
|
|
// p := tea.NewProgram(initialModel())
|
|
|
|
// if _, err := p.Run(); err != nil {
|
|
|
|
// fmt.Printf("Alas, there's been an error: %v", err)
|
|
|
|
// os.Exit(1)
|
|
|
|
// }
|
2024-08-03 18:06:43 -07:00
|
|
|
}
|