mirror of
https://github.com/wisplite/parchment.git
synced 2026-06-27 13:47:08 -05:00
initial commit
This commit is contained in:
@@ -0,0 +1,328 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// AppStatus represents the current state of the application.
|
||||
type AppStatus int
|
||||
|
||||
const (
|
||||
StatusSearching AppStatus = iota
|
||||
StatusFound
|
||||
StatusConnecting
|
||||
StatusConnected
|
||||
StatusError
|
||||
StatusUploading
|
||||
StatusUploaded
|
||||
StatusPromptUnpack
|
||||
StatusUnpacking
|
||||
StatusUnpacked
|
||||
)
|
||||
|
||||
// UIModel is the Bubble Tea model for the application.
|
||||
type UIModel struct {
|
||||
Device *Device
|
||||
Status AppStatus
|
||||
ErrorMsg string
|
||||
TextInput textinput.Model
|
||||
Width int
|
||||
Height int
|
||||
}
|
||||
|
||||
// Custom messages for Bubble Tea commands
|
||||
type connectMsg struct {
|
||||
err error
|
||||
}
|
||||
|
||||
type uploadMsg struct {
|
||||
err error
|
||||
}
|
||||
|
||||
type unpackMsg struct {
|
||||
err error
|
||||
}
|
||||
|
||||
// InitialModel creates the initial state of the model.
|
||||
func InitialModel(device *Device) UIModel {
|
||||
ti := textinput.New()
|
||||
ti.Placeholder = "package-name"
|
||||
ti.CharLimit = 256
|
||||
ti.Width = 40
|
||||
|
||||
return UIModel{
|
||||
Device: device,
|
||||
Status: StatusSearching,
|
||||
TextInput: ti,
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes the model.
|
||||
func (m UIModel) Init() tea.Cmd {
|
||||
return textinput.Blink
|
||||
}
|
||||
|
||||
// Update handles messages and updates the model.
|
||||
func (m UIModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.Width = msg.Width
|
||||
m.Height = msg.Height
|
||||
return m, nil
|
||||
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "ctrl+c", "q":
|
||||
// Ensure device is closed on exit
|
||||
if m.Device != nil {
|
||||
m.Device.Close()
|
||||
}
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
|
||||
// Handle input based on current status
|
||||
if m.Status == StatusPromptUnpack {
|
||||
return m.updatePromptUnpack(msg)
|
||||
}
|
||||
|
||||
return m.updateMain(msg)
|
||||
}
|
||||
|
||||
func (m UIModel) updatePromptUnpack(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "esc":
|
||||
if m.Device.Connected {
|
||||
m.Status = StatusConnected
|
||||
} else {
|
||||
m.Status = StatusFound
|
||||
}
|
||||
m.TextInput.Reset()
|
||||
return m, nil
|
||||
case "enter":
|
||||
filename := m.TextInput.Value()
|
||||
if filename != "" {
|
||||
m.Status = StatusUnpacking
|
||||
m.TextInput.Reset()
|
||||
return m, debugUnpackCmd(filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
m.TextInput, cmd = m.TextInput.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m UIModel) updateMain(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "c", "enter":
|
||||
if m.Status == StatusFound {
|
||||
m.Status = StatusConnecting
|
||||
return m, connectCmd(m.Device)
|
||||
}
|
||||
case "u":
|
||||
if m.Status == StatusConnected {
|
||||
m.Status = StatusUploading
|
||||
return m, uploadCmd(m.Device)
|
||||
}
|
||||
case "d":
|
||||
if m.Status == StatusFound || m.Status == StatusConnected || m.Status == StatusUploaded || m.Status == StatusUnpacked {
|
||||
m.Status = StatusPromptUnpack
|
||||
m.TextInput.Focus()
|
||||
return m, textinput.Blink
|
||||
}
|
||||
}
|
||||
|
||||
case connectMsg:
|
||||
if msg.err != nil {
|
||||
m.Status = StatusError
|
||||
m.ErrorMsg = msg.err.Error()
|
||||
return m, nil
|
||||
}
|
||||
m.Status = StatusConnected
|
||||
return m, nil
|
||||
|
||||
case uploadMsg:
|
||||
if msg.err != nil {
|
||||
m.Status = StatusError
|
||||
m.ErrorMsg = msg.err.Error()
|
||||
return m, nil
|
||||
}
|
||||
m.Status = StatusUploaded
|
||||
return m, nil
|
||||
|
||||
case unpackMsg:
|
||||
if msg.err != nil {
|
||||
m.Status = StatusError
|
||||
m.ErrorMsg = msg.err.Error()
|
||||
return m, nil
|
||||
}
|
||||
m.Status = StatusUnpacked
|
||||
return m, nil
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// View renders the application UI.
|
||||
func (m UIModel) View() string {
|
||||
var s strings.Builder
|
||||
|
||||
// Styles
|
||||
titleStyle := lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("#FAFAFA")).
|
||||
Background(lipgloss.Color("#7D56F4")).
|
||||
Padding(0, 1).
|
||||
MarginBottom(1)
|
||||
|
||||
statusStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#7D56F4")).Bold(true)
|
||||
errorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#FF0000")).Bold(true)
|
||||
successStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#00FF00")).Bold(true)
|
||||
keyStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#626262")).Bold(true)
|
||||
descStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#A0A0A0"))
|
||||
|
||||
// Header
|
||||
s.WriteString(titleStyle.Render("Parchment - Cardstock Dev Server"))
|
||||
s.WriteString("\n")
|
||||
|
||||
// Content
|
||||
content := strings.Builder{}
|
||||
|
||||
switch m.Status {
|
||||
case StatusSearching:
|
||||
content.WriteString(statusStyle.Render("⏳ Searching for Cardputer..."))
|
||||
|
||||
case StatusFound:
|
||||
content.WriteString(successStyle.Render("✓ Cardputer found"))
|
||||
content.WriteString(fmt.Sprintf(" at %s\n\n", m.Device.PortName))
|
||||
content.WriteString(keyStyle.Render("[c] / [enter]") + descStyle.Render(" Connect and enter dev mode\n"))
|
||||
content.WriteString(keyStyle.Render("[d]") + descStyle.Render(" Debug pack/unpack (local)\n"))
|
||||
|
||||
case StatusConnecting:
|
||||
content.WriteString(statusStyle.Render("⏳ Connecting and requesting dev mode..."))
|
||||
|
||||
case StatusConnected:
|
||||
content.WriteString(successStyle.Render("✓ Connected to Cardputer!"))
|
||||
content.WriteString(fmt.Sprintf("\n\nPort: %s\n", m.Device.PortName))
|
||||
content.WriteString("Device is in developer mode.\n\n")
|
||||
content.WriteString(keyStyle.Render("[u]") + descStyle.Render(" Upload package from current directory\n"))
|
||||
content.WriteString(keyStyle.Render("[d]") + descStyle.Render(" Debug pack/unpack (local)\n"))
|
||||
|
||||
case StatusUploading:
|
||||
content.WriteString(statusStyle.Render("⏳ Uploading package to device..."))
|
||||
|
||||
case StatusUploaded:
|
||||
content.WriteString(successStyle.Render("✓ Package uploaded successfully!"))
|
||||
content.WriteString(fmt.Sprintf("\n\nPort: %s\n", m.Device.PortName))
|
||||
content.WriteString("\n")
|
||||
content.WriteString(keyStyle.Render("[d]") + descStyle.Render(" Debug pack/unpack\n"))
|
||||
|
||||
case StatusPromptUnpack:
|
||||
content.WriteString(statusStyle.Render("Debug Unpack"))
|
||||
content.WriteString("\n\nEnter the filename to unpack (without .cpkg extension):\n\n")
|
||||
content.WriteString(m.TextInput.View())
|
||||
content.WriteString("\n\n")
|
||||
content.WriteString(keyStyle.Render("[enter]") + descStyle.Render(" Unpack\n"))
|
||||
content.WriteString(keyStyle.Render("[esc]") + descStyle.Render(" Cancel\n"))
|
||||
|
||||
case StatusUnpacking:
|
||||
content.WriteString(statusStyle.Render("⏳ Debug packing and unpacking..."))
|
||||
|
||||
case StatusUnpacked:
|
||||
content.WriteString(successStyle.Render("✓ Debug pack/unpack completed!"))
|
||||
content.WriteString("\n\nCheck debug.log for details.\n")
|
||||
content.WriteString(keyStyle.Render("[d]") + descStyle.Render(" Run again\n"))
|
||||
|
||||
case StatusError:
|
||||
content.WriteString(errorStyle.Render("✗ Error: "))
|
||||
content.WriteString(m.ErrorMsg)
|
||||
content.WriteString("\n\n")
|
||||
if m.Device.Connected {
|
||||
content.WriteString(keyStyle.Render("[u]") + descStyle.Render(" Retry upload\n"))
|
||||
} else {
|
||||
content.WriteString(keyStyle.Render("[c]") + descStyle.Render(" Retry connection\n"))
|
||||
}
|
||||
}
|
||||
|
||||
// Render content with some margin
|
||||
s.WriteString(lipgloss.NewStyle().Margin(1, 0, 1, 0).Render(content.String()))
|
||||
|
||||
// Footer
|
||||
s.WriteString("\n")
|
||||
footer := lipgloss.NewStyle().Foreground(lipgloss.Color("#626262")).Render("Press [q] or [ctrl+c] to quit")
|
||||
s.WriteString(footer)
|
||||
|
||||
return lipgloss.NewStyle().Padding(1, 2).Render(s.String())
|
||||
}
|
||||
|
||||
// Commands
|
||||
|
||||
func connectCmd(d *Device) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
if err := d.Connect(); err != nil {
|
||||
return connectMsg{err: err}
|
||||
}
|
||||
return connectMsg{err: nil}
|
||||
}
|
||||
}
|
||||
|
||||
func uploadCmd(d *Device) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
// Mock upload for now or implement actual upload logic using Device
|
||||
// We'll use the existing logic from main.go adapted here
|
||||
fsys := os.DirFS(".")
|
||||
pkgData, err := loadPackageJSON(fsys)
|
||||
if err != nil {
|
||||
return uploadMsg{err: err}
|
||||
}
|
||||
if pkgData == nil {
|
||||
return uploadMsg{err: fmt.Errorf("package.json not found")}
|
||||
}
|
||||
|
||||
log.Println("Loading package data:", pkgData)
|
||||
pkg, err := createCpkg(fsys, pkgData)
|
||||
if err != nil {
|
||||
return uploadMsg{err: err}
|
||||
}
|
||||
pkg.DebugPrint()
|
||||
|
||||
// TODO: Actually send the package over serial using d.Write()
|
||||
// For now we just simulate success as per original code
|
||||
|
||||
return uploadMsg{err: nil}
|
||||
}
|
||||
}
|
||||
|
||||
func debugUnpackCmd(filename string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
fsys := os.DirFS(".")
|
||||
|
||||
packageName := strings.TrimSuffix(filename, ".cpkg")
|
||||
log.Printf("Attempting to unpack: %s.cpkg", packageName)
|
||||
|
||||
if _, err := os.Stat(packageName + ".cpkg"); os.IsNotExist(err) {
|
||||
return unpackMsg{err: fmt.Errorf("file not found: %s.cpkg", packageName)}
|
||||
}
|
||||
|
||||
unpackPkg := NewCpkg(packageName)
|
||||
err := unpackPkg.Unpack(fsys)
|
||||
if err != nil {
|
||||
return unpackMsg{err: fmt.Errorf("failed to unpack: %w", err)}
|
||||
}
|
||||
|
||||
log.Println("Debug unpack completed successfully")
|
||||
return unpackMsg{err: nil}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user