first draft of miasma installer

This commit is contained in:
tumillanino
2025-10-18 17:13:02 +11:00
commit 8f054b63a1
8 changed files with 335 additions and 0 deletions

70
tui/model.go Normal file
View File

@@ -0,0 +1,70 @@
package tui
import (
"miasma-installer/tui/steps"
tea "github.com/charmbracelet/bubbletea"
)
type State int
const (
StateWelcome State = iota
StateDiskSelection
StateInstalling
StateFinished
)
type RootModel struct {
State State
CurrentModel tea.Model
Width int
Height int
}
func NewModel() RootModel {
return RootModel{
State: StateWelcome,
CurrentModel: steps.NewWelcomeModel(),
}
}
func (m RootModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
if msg.String() == "ctrl+c" {
return m, tea.Quit
}
case tea.WindowSizeMsg:
m.Width = msg.Width
m.Height = msg.Height
}
m.CurrentModel, cmd = m.CurrentModel.Update(msg)
switch m.CurrentModel.(type) {
case steps.WelcomeModel:
if m.CurrentModel.(steps.WelcomeModel).Finished {
m.State = StateDiskSelection
m.CurrentModel = steps.NewDiskSelModel(m.Width, m.Height)
return m, m.CurrentModel.Init()
}
case steps.DiskSelModel:
if m.CurrentModel.(steps.DiskSelModel).Finished {
// need to update this to pass actual selected disk model to the installer
selectedDisk := m.CurrentModel.(steps.DiskSelModel).SelectedDisk
m.State = StateInstalling
m.CurrentModel = steps.NewInstallModel(selectedDisk)
return m, m.CurrentModel.Init()
}
// logic for StateInstalling -> StateFinished will go here
}
return m, cmd
}
func (m RootModel) View() string {
return m.CurrentModel.View()
}

94
tui/steps/disk_sel.go Normal file
View File

@@ -0,0 +1,94 @@
package steps
import (
"fmt"
"io"
"miasma-installer/tui/styles"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
)
type Disk struct {
name, description string
}
func (i Disk) FilterValue() string { return i.name }
func (i Disk) Title() string { return i.name }
func (i Disk) Description() string { return i.description }
type ItemDelegate struct{}
func (i ItemDelegate) Height() int { return 1 }
func (i ItemDelegate) Width() int { return 0 }
func (i ItemDelegate) Update(msg tea.Msg, m list.Model) tea.Cmd { return nil }
func (i ItemDelegate) Render(w io.Writer, m list.Model, index int, item list.Item) {
i, ok := item.(Disk)
if !ok {
return
}
s := fmt.Sprintf("%d. %s - %s", index+1, i.Title(), i.Description())
// focus styles
if index == m.Index() {
s = styles.ActiveItemStyle.Render("> " + s)
} else {
s = styles.InactiveItemStyle.Render(" " + s)
}
fmt.Fprint(w, s)
}
type DiskSelModel struct {
list list.Model
Finished bool
SelectedDisk string
}
func NewDiskSelModel(width, height int) DiskSelModel {
// This is a placeholder - I need to update this to a tea.Cmd to scan the system for drives
disks := []list.Item{
Disk{name: "/dev/sda", description: "500GB Samsung NVME"},
Disk{name: "/dev/sdb", description: "1TB Baracuda HDD"},
Disk{name: "/dev/loop0", description: "2GB Fanxiang SSD"},
}
l := list.New(disks, itemDelegate{}, width, height-5)
l.Title = styles.TitleStyle.Render("Select Installation Disk")
l.SetShowStatusBar(false)
l.SetFilterEnabled(false)
l.Styles.Title = styles.TitleStyle
l.KeyMap.Unbind()
return DiskSelModel{list: l}
}
func (m DiskSelModel) Init() tea.Cmd {
return nil
}
func (m DiskSelModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.Finished {
return m, nil
}
switch msg := msg.(type) {
case tea.KeyMsg:
if msg.String() == "enter" {
if item, ok := m.list.SelectedItem().(Disk); ok {
m.SelectedDisk = item.name
m.Finished = true
return m, nil
}
}
}
var cmd tea.Cmd
m.list, cmd = m.list.Update(msg)
return m, cmd
}
func (m DiskSelModel) View() string {
s := m.list.View()
s += "\n" + styles.HelpStyle.Render("Use ↑/↓ to navigate, [Enter] to select disk.")
return s
}

0
tui/steps/install.go Normal file
View File

46
tui/steps/welcome.go Normal file
View File

@@ -0,0 +1,46 @@
package steps
import (
"miasma-installer/tui/styles"
tea "github.com/charmbracelet/bubbletea"
)
type WelcomeModel struct {
Finished bool
}
func NewWelcomeModel() WelcomeModel {
return WelcomeModel{Finished: false}
}
func (m WelcomeModel) Init() tea.Cmd {
return nil
}
func (m WelcomeModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.Finished {
return m, nil
}
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "enter":
m.Finished = true
}
}
return m, nil
}
func (m WelcomeModel) View() string {
s := styles.TitleStyle.Render("Welcome to the Miasma OS Installer")
s += "\n\n"
s += "This program will guide you through installing Miasma OS. \n"
s += "Please ensure that you have backed up any important data before proceding"
s += "\n\n"
s += styles.HelpStyle.Render("Press [Enter] to continue")
s += styles.HelpStyle.Render("Press [Ctrl+C] to exit")
return styles.MainStyle.Render(s)
}

25
tui/styles/styles.go Normal file
View File

@@ -0,0 +1,25 @@
package styles
import "github.com/charmbracelet/lipgloss"
var (
MainStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("#7fb4ca")).
Padding(1, 2)
HelpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
TitleStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#7fb4ca")).
Align(lipgloss.Center).
Bold(true).
PaddingBottom(1)
ActiveItemStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#87a987")).
Bold(true)
InactiveItemStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("240"))
)