From e77eaf4e2cec824ebd526f53afdc11f47f7e827b Mon Sep 17 00:00:00 2001 From: Hadi <112569860+anotherhadi@users.noreply.github.com> Date: Thu, 20 Aug 2026 20:28:24 +0200 Subject: [PATCH] Move UI & make a root model. Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com> --- app.go | 55 ++++++++++++++++++++++++++ flake.nix | 2 +- go.mod | 2 +- go.sum | 4 +- main.go | 3 +- {internal/ui => ui}/actionmodal.go | 0 {internal/ui => ui}/delegate.go | 0 {internal/ui => ui}/icons.go | 0 {internal/ui => ui}/keys.go | 0 {internal/ui => ui}/model.go | 51 ++++++------------------ {internal/ui => ui}/permissionmodal.go | 6 +-- {internal/ui => ui}/styles.go | 0 12 files changed, 76 insertions(+), 47 deletions(-) create mode 100644 app.go rename {internal/ui => ui}/actionmodal.go (100%) rename {internal/ui => ui}/delegate.go (100%) rename {internal/ui => ui}/icons.go (100%) rename {internal/ui => ui}/keys.go (100%) rename {internal/ui => ui}/model.go (92%) rename {internal/ui => ui}/permissionmodal.go (87%) rename {internal/ui => ui}/styles.go (100%) diff --git a/app.go b/app.go new file mode 100644 index 0000000..599e083 --- /dev/null +++ b/app.go @@ -0,0 +1,55 @@ +package main + +import ( + tea "charm.land/bubbletea/v2" + "github.com/anotherhadi/ilovetui/app" + "github.com/anotherhadi/ilovetui/modal" + "github.com/anotherhadi/ilovetui/notification" + "github.com/anotherhadi/usbguard-tui/ui" +) + +type appModel struct { + core ui.Model + modals modal.Model + notif notification.Model +} + +func newApp() appModel { + return appModel{ + core: ui.New(), + modals: modal.New(), + notif: notification.New(), + } +} + +func (a appModel) Init() tea.Cmd { + return tea.Batch(a.core.Init(), a.modals.Init(), a.notif.Init()) +} + +func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + if _, ok := msg.(app.QuitMsg); ok { + return a, tea.Quit + } + + a.core.ModalOpen = a.modals.Open() + var coreCmd tea.Cmd + a.core, coreCmd = a.core.Update(msg) + + var modalCmd, notifCmd tea.Cmd + a.modals, modalCmd = a.modals.Update(msg) + a.notif, notifCmd = a.notif.Update(msg) + + return a, tea.Batch(coreCmd, modalCmd, notifCmd) +} + +func (a appModel) View() tea.View { + bg := a.core.View() + bg = a.modals.Render(bg) + bg = a.notif.Render(bg) + return tea.View{ + Content: bg, + AltScreen: true, + WindowTitle: "USBGuard TUI", + MouseMode: tea.MouseModeCellMotion, + } +} diff --git a/flake.nix b/flake.nix index d290bb5..62e8a14 100644 --- a/flake.nix +++ b/flake.nix @@ -25,7 +25,7 @@ src = ./.; outputs = ["out"]; - vendorHash = "sha256-tXMeJy9IpXTRhikYedcL+76H9X3In9mb1/KnN1XFPu4="; + vendorHash = "sha256-8QP2FbNmvqrJhFe3Ia7tPhkxMeFWBhvr4Zr9kpV9bR4="; meta = with pkgs.lib; { description = "A terminal UI for managing USB devices via usbguard."; diff --git a/go.mod b/go.mod index 9cfd935..b650be4 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( charm.land/bubbles/v2 v2.1.0 charm.land/bubbletea/v2 v2.0.6 charm.land/lipgloss/v2 v2.0.3 - github.com/anotherhadi/ilovetui v0.2.1 + github.com/anotherhadi/ilovetui v0.2.2 github.com/charmbracelet/x/ansi v0.11.7 ) diff --git a/go.sum b/go.sum index d8d147c..b7c3183 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,8 @@ github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46 github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/anotherhadi/ilovetui v0.2.1 h1:ndU52r0nzlT6LM53HNBvwVyvn3dRPKrbGVvzux9YXbk= -github.com/anotherhadi/ilovetui v0.2.1/go.mod h1:gAn69amLM3z0x8PV4obyf5bJntL/PTZusEuhu/xdFIY= +github.com/anotherhadi/ilovetui v0.2.2 h1:ZOwyZRxFBZKVJeEAR+lg0xeQfsMZuymtKLwBV0MnBYY= +github.com/anotherhadi/ilovetui v0.2.2/go.mod h1:gAn69amLM3z0x8PV4obyf5bJntL/PTZusEuhu/xdFIY= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o= diff --git a/main.go b/main.go index 66ada4e..e28b7b2 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,6 @@ import ( tea "charm.land/bubbletea/v2" "github.com/anotherhadi/usbguard-tui/internal/guard" - "github.com/anotherhadi/usbguard-tui/internal/ui" ) var version = "dev" @@ -22,7 +21,7 @@ func main() { os.Exit(1) } - p := tea.NewProgram(ui.New()) + p := tea.NewProgram(newApp()) if _, err := p.Run(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) diff --git a/internal/ui/actionmodal.go b/ui/actionmodal.go similarity index 100% rename from internal/ui/actionmodal.go rename to ui/actionmodal.go diff --git a/internal/ui/delegate.go b/ui/delegate.go similarity index 100% rename from internal/ui/delegate.go rename to ui/delegate.go diff --git a/internal/ui/icons.go b/ui/icons.go similarity index 100% rename from internal/ui/icons.go rename to ui/icons.go diff --git a/internal/ui/keys.go b/ui/keys.go similarity index 100% rename from internal/ui/keys.go rename to ui/keys.go diff --git a/internal/ui/model.go b/ui/model.go similarity index 92% rename from internal/ui/model.go rename to ui/model.go index 63e0440..5d2adfe 100644 --- a/internal/ui/model.go +++ b/ui/model.go @@ -12,6 +12,7 @@ import ( "charm.land/lipgloss/v2" "github.com/charmbracelet/x/ansi" + "github.com/anotherhadi/ilovetui/app" "github.com/anotherhadi/ilovetui/bubbles" "github.com/anotherhadi/ilovetui/helpbar" "github.com/anotherhadi/ilovetui/modal" @@ -41,8 +42,6 @@ type deviceSummary struct { type Model struct { list list.Model help helpbar.Model - modals modal.Model - notif notification.Model daemonStatus string defaultPolicy guard.Status deviceCounts deviceSummary @@ -52,6 +51,8 @@ type Model struct { rulesWritable bool pendingRules []pendingRule fatalShown bool + + ModalOpen bool } func New() Model { @@ -76,8 +77,6 @@ func New() Model { return Model{ list: l, help: h, - modals: modal.New(), - notif: notification.New(), rulesManaged: rulesManaged, rulesWritable: rulesWritable, } @@ -117,21 +116,10 @@ func makeActionList(rulesManaged bool) list.Model { } func (m Model) Init() tea.Cmd { - return tea.Batch(fetchDevices, fetchDaemonStatus, fetchDefaultPolicy, tickCmd(), m.modals.Init(), m.notif.Init()) + return tea.Batch(fetchDevices, fetchDaemonStatus, fetchDefaultPolicy, tickCmd(), app.SetTitle("USBGuard TUI")) } -func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - next, cmd := m.updateMain(msg) - nm := next.(Model) - - var modalCmd, notifCmd tea.Cmd - nm.modals, modalCmd = nm.modals.Update(msg) - nm.notif, notifCmd = nm.notif.Update(msg) - - return nm, tea.Batch(cmd, modalCmd, notifCmd) -} - -func (m Model) updateMain(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: @@ -200,13 +188,13 @@ func (m Model) updateMain(msg tea.Msg) (tea.Model, tea.Cmd) { return m, fetchDevices case tea.KeyPressMsg: - if m.modals.Open() { + if m.ModalOpen { return m, nil } return m.updateList(msg) case tea.MouseWheelMsg: - if m.modals.Open() { + if m.ModalOpen { return m, nil } switch msg.Button { @@ -218,7 +206,7 @@ func (m Model) updateMain(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } - if m.modals.Open() { + if m.ModalOpen { return m, nil } var cmd tea.Cmd @@ -226,15 +214,15 @@ func (m Model) updateMain(msg tea.Msg) (tea.Model, tea.Cmd) { return m, cmd } -func (m Model) updateList(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { +func (m Model) updateList(msg tea.KeyPressMsg) (Model, tea.Cmd) { if msg.String() == "ctrl+c" { - return m, tea.Quit + return m, app.Quit() } if !m.list.SettingFilter() { dev, hasSelection := m.selectedDevice() switch { case key.Matches(msg, listKeys.Quit): - return m, tea.Quit + return m, app.Quit() case key.Matches(msg, listKeys.Refresh): return m, tea.Batch(fetchDevices, fetchDaemonStatus, fetchDefaultPolicy) case key.Matches(msg, listKeys.Help): @@ -274,24 +262,11 @@ func (m Model) updateList(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { return m, cmd } -func (m Model) View() tea.View { - return tea.View{ - Content: m.renderContent(), - AltScreen: true, - WindowTitle: "USBGuard TUI", - MouseMode: tea.MouseModeCellMotion, - } -} - -func (m Model) renderContent() string { +func (m Model) View() string { header := m.renderHeader() listView := strings.TrimRight(m.list.View(), "\n") helpView := strings.TrimRight(m.help.View(), "\n") - bg := strings.Join([]string{header, listView, helpView}, "\n") - - bg = m.modals.Render(bg) - bg = m.notif.Render(bg) - return bg + return strings.Join([]string{header, listView, helpView}, "\n") } func (m Model) renderHeader() string { diff --git a/internal/ui/permissionmodal.go b/ui/permissionmodal.go similarity index 87% rename from internal/ui/permissionmodal.go rename to ui/permissionmodal.go index b5f8f7a..ed97a74 100644 --- a/internal/ui/permissionmodal.go +++ b/ui/permissionmodal.go @@ -5,6 +5,7 @@ import ( tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" + "github.com/anotherhadi/ilovetui/app" "github.com/anotherhadi/ilovetui/modal" "github.com/anotherhadi/ilovetui/style" ) @@ -25,7 +26,7 @@ func (permissionModal) Init() tea.Cmd { return nil } func (m permissionModal) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg.(type) { case tea.KeyPressMsg, tea.MouseClickMsg: - return m, tea.Quit + return m, app.Quit() } return m, nil } @@ -35,8 +36,7 @@ func (permissionModal) View() tea.View { hintStyle := lipgloss.NewStyle().Foreground(style.S.Muted) parts := []string{ - textStyle.Render("Can't reach the usbguard daemon"), - textStyle.Render("(permission denied)."), + textStyle.Render("Can't reach the usbguard daemon (permission denied)."), textStyle.Render("Join the usbguard group, or use sudo."), hintStyle.Render("press any key to quit"), } diff --git a/internal/ui/styles.go b/ui/styles.go similarity index 100% rename from internal/ui/styles.go rename to ui/styles.go