mirror of
https://github.com/anotherhadi/spilltea.git
synced 2026-05-20 09:42:34 +02:00
Init
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"github.com/anotherhadi/spilltea/internal/config"
|
||||
"github.com/anotherhadi/spilltea/internal/db"
|
||||
"github.com/anotherhadi/spilltea/internal/intercept"
|
||||
"github.com/anotherhadi/spilltea/internal/plugins"
|
||||
proxyPkg "github.com/anotherhadi/spilltea/internal/proxy"
|
||||
copyasUI "github.com/anotherhadi/spilltea/internal/ui/components/copyas"
|
||||
notificationsUI "github.com/anotherhadi/spilltea/internal/ui/components/notifications"
|
||||
diffUI "github.com/anotherhadi/spilltea/internal/ui/diff"
|
||||
docsUI "github.com/anotherhadi/spilltea/internal/ui/docs"
|
||||
findingsUI "github.com/anotherhadi/spilltea/internal/ui/findings"
|
||||
historyUI "github.com/anotherhadi/spilltea/internal/ui/history"
|
||||
interceptUI "github.com/anotherhadi/spilltea/internal/ui/intercept"
|
||||
pluginsUI "github.com/anotherhadi/spilltea/internal/ui/plugins"
|
||||
replayUI "github.com/anotherhadi/spilltea/internal/ui/replay"
|
||||
scopeUI "github.com/anotherhadi/spilltea/internal/ui/scope"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const tickInterval = 2 * time.Second
|
||||
|
||||
type tickMsg struct{}
|
||||
|
||||
func tickCmd() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
time.Sleep(tickInterval)
|
||||
return tickMsg{}
|
||||
}
|
||||
}
|
||||
|
||||
var sidebarEntries = pageRegistry
|
||||
|
||||
var pageShortcuts = func() map[string]page {
|
||||
m := make(map[string]page, len(sidebarEntries))
|
||||
for i, e := range sidebarEntries {
|
||||
m[strconv.Itoa(i+1)] = e.id
|
||||
}
|
||||
return m
|
||||
}()
|
||||
|
||||
type Model struct {
|
||||
broker *intercept.Broker
|
||||
page page
|
||||
projectName string
|
||||
projectPath string
|
||||
database *db.DB
|
||||
logFile *os.File
|
||||
pluginManager *plugins.Manager
|
||||
|
||||
width int
|
||||
height int
|
||||
sidebarState sidebarState
|
||||
intercept interceptUI.Model
|
||||
history historyUI.Model
|
||||
replay replayUI.Model
|
||||
diff diffUI.Model
|
||||
docs docsUI.Model
|
||||
scope scopeUI.Model
|
||||
pluginsPage pluginsUI.Model
|
||||
findingsPage findingsUI.Model
|
||||
copyAs copyasUI.Model
|
||||
notifications notificationsUI.Model
|
||||
}
|
||||
|
||||
func New(broker *intercept.Broker, name, path string) Model {
|
||||
cfg := config.Global
|
||||
mgr := plugins.NewManager(broker)
|
||||
|
||||
m := Model{
|
||||
broker: broker,
|
||||
page: pageIntercept,
|
||||
projectName: name,
|
||||
projectPath: path,
|
||||
pluginManager: mgr,
|
||||
intercept: interceptUI.New(broker),
|
||||
history: historyUI.New(),
|
||||
replay: replayUI.New(),
|
||||
diff: diffUI.New(),
|
||||
docs: docsUI.New(),
|
||||
scope: scopeUI.New(name, path),
|
||||
pluginsPage: pluginsUI.New(mgr),
|
||||
findingsPage: findingsUI.New(),
|
||||
copyAs: copyasUI.New(),
|
||||
notifications: notificationsUI.New(),
|
||||
sidebarState: sidebarState(cfg.TUI.DefaultSidebarState),
|
||||
}
|
||||
|
||||
if d, err := db.Open(path); err == nil {
|
||||
m.database = d
|
||||
broker.SetDB(d)
|
||||
m.history.SetDB(d)
|
||||
m.replay.SetDB(d)
|
||||
m.findingsPage.SetDB(d)
|
||||
mgr.SetDB(d)
|
||||
if wl, bl, err := d.LoadScope(); err == nil {
|
||||
broker.SetScope(wl, bl)
|
||||
m.scope.SetScope(wl, bl)
|
||||
}
|
||||
}
|
||||
|
||||
pluginsDir := config.ExpandPath(cfg.App.PluginsDir)
|
||||
if err := mgr.LoadFromDir(pluginsDir); err != nil {
|
||||
log.Printf("plugins: %v", err)
|
||||
}
|
||||
m.pluginsPage.Refresh()
|
||||
|
||||
if lf, err := os.OpenFile(filepath.Join(filepath.Dir(path), "logs.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600); err == nil {
|
||||
m.logFile = lf
|
||||
log.SetOutput(lf)
|
||||
logrus.SetOutput(lf)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (m Model) Init() tea.Cmd {
|
||||
mgr := m.pluginManager
|
||||
return tea.Batch(
|
||||
intercept.WaitForRequest(m.broker),
|
||||
intercept.WaitForResponse(m.broker),
|
||||
tickCmd(),
|
||||
proxyPkg.StartCmd(m.broker, mgr),
|
||||
plugins.WaitForNotif(mgr),
|
||||
plugins.WaitForQuit(mgr),
|
||||
findingsUI.RefreshCmd(m.database),
|
||||
func() tea.Msg { mgr.RunOnStart(); return nil },
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"github.com/anotherhadi/spilltea/internal/icons"
|
||||
diffUI "github.com/anotherhadi/spilltea/internal/ui/diff"
|
||||
docsUI "github.com/anotherhadi/spilltea/internal/ui/docs"
|
||||
findingsUI "github.com/anotherhadi/spilltea/internal/ui/findings"
|
||||
historyUI "github.com/anotherhadi/spilltea/internal/ui/history"
|
||||
interceptUI "github.com/anotherhadi/spilltea/internal/ui/intercept"
|
||||
pluginsUI "github.com/anotherhadi/spilltea/internal/ui/plugins"
|
||||
replayUI "github.com/anotherhadi/spilltea/internal/ui/replay"
|
||||
scopeUI "github.com/anotherhadi/spilltea/internal/ui/scope"
|
||||
)
|
||||
|
||||
type page string
|
||||
|
||||
const (
|
||||
pageIntercept page = "Intercept"
|
||||
pageHistory page = "History"
|
||||
pageReplay page = "Replay"
|
||||
pageDiff page = "Diff"
|
||||
pageScopes page = "Scopes"
|
||||
pagePlugins page = "Plugins"
|
||||
pageFindings page = "Findings"
|
||||
pageDocs page = "Docs"
|
||||
)
|
||||
|
||||
// pageEntry describes a page and all its integration hooks.
|
||||
type pageEntry struct {
|
||||
id page
|
||||
icon func() string
|
||||
|
||||
// render returns the page's view content. nil = show "empty".
|
||||
render func(m *Model) string
|
||||
// update is called when this page is active. nil = no-op.
|
||||
update func(m *Model, msg tea.Msg) tea.Cmd
|
||||
// isEditing reports whether the page is in text-editing mode.
|
||||
isEditing func(m *Model) bool
|
||||
// resize propagates a new (w, h) to the page model.
|
||||
resize func(m *Model, w, h int)
|
||||
}
|
||||
|
||||
var pageRegistry = []pageEntry{
|
||||
{
|
||||
id: pageIntercept,
|
||||
icon: func() string { return icons.I.Intercept },
|
||||
|
||||
render: func(m *Model) string { return m.intercept.View().Content },
|
||||
update: func(m *Model, msg tea.Msg) tea.Cmd {
|
||||
updated, cmd := m.intercept.Update(msg)
|
||||
m.intercept = updated.(interceptUI.Model)
|
||||
return cmd
|
||||
},
|
||||
isEditing: func(m *Model) bool { return m.intercept.IsEditing() },
|
||||
resize: func(m *Model, w, h int) { m.intercept.SetSize(w, h) },
|
||||
},
|
||||
{
|
||||
id: pageHistory,
|
||||
icon: func() string { return icons.I.History },
|
||||
|
||||
render: func(m *Model) string { return m.history.View().Content },
|
||||
update: func(m *Model, msg tea.Msg) tea.Cmd {
|
||||
updated, cmd := m.history.Update(msg)
|
||||
m.history = updated.(historyUI.Model)
|
||||
return cmd
|
||||
},
|
||||
isEditing: func(m *Model) bool { return m.history.IsEditing() },
|
||||
resize: func(m *Model, w, h int) { m.history.SetSize(w, h) },
|
||||
},
|
||||
{
|
||||
id: pageReplay,
|
||||
icon: func() string { return icons.I.Replay },
|
||||
|
||||
render: func(m *Model) string { return m.replay.View().Content },
|
||||
update: func(m *Model, msg tea.Msg) tea.Cmd {
|
||||
updated, cmd := m.replay.Update(msg)
|
||||
m.replay = updated.(replayUI.Model)
|
||||
return cmd
|
||||
},
|
||||
isEditing: func(m *Model) bool { return m.replay.IsEditing() },
|
||||
resize: func(m *Model, w, h int) { m.replay.SetSize(w, h) },
|
||||
},
|
||||
{
|
||||
id: pageDiff,
|
||||
icon: func() string { return icons.I.Diff },
|
||||
|
||||
render: func(m *Model) string { return m.diff.View().Content },
|
||||
update: func(m *Model, msg tea.Msg) tea.Cmd {
|
||||
updated, cmd := m.diff.Update(msg)
|
||||
m.diff = updated.(diffUI.Model)
|
||||
return cmd
|
||||
},
|
||||
resize: func(m *Model, w, h int) { m.diff.SetSize(w, h) },
|
||||
},
|
||||
{
|
||||
id: pageScopes,
|
||||
icon: func() string { return icons.I.Scope },
|
||||
|
||||
render: func(m *Model) string { return m.scope.View().Content },
|
||||
update: func(m *Model, msg tea.Msg) tea.Cmd {
|
||||
updated, cmd := m.scope.Update(msg)
|
||||
m.scope = updated.(scopeUI.Model)
|
||||
return cmd
|
||||
},
|
||||
isEditing: func(m *Model) bool { return m.scope.IsEditing() },
|
||||
resize: func(m *Model, w, h int) { m.scope.SetSize(w, h) },
|
||||
},
|
||||
{
|
||||
id: pagePlugins,
|
||||
icon: func() string { return icons.I.Plugin },
|
||||
|
||||
render: func(m *Model) string { return m.pluginsPage.View().Content },
|
||||
update: func(m *Model, msg tea.Msg) tea.Cmd {
|
||||
updated, cmd := m.pluginsPage.Update(msg)
|
||||
m.pluginsPage = updated.(pluginsUI.Model)
|
||||
return cmd
|
||||
},
|
||||
isEditing: func(m *Model) bool { return m.pluginsPage.IsEditing() },
|
||||
resize: func(m *Model, w, h int) { m.pluginsPage.SetSize(w, h) },
|
||||
},
|
||||
{
|
||||
id: pageFindings,
|
||||
icon: func() string { return icons.I.Findings },
|
||||
|
||||
render: func(m *Model) string { return m.findingsPage.View().Content },
|
||||
update: func(m *Model, msg tea.Msg) tea.Cmd {
|
||||
updated, cmd := m.findingsPage.Update(msg)
|
||||
m.findingsPage = updated.(findingsUI.Model)
|
||||
return cmd
|
||||
},
|
||||
resize: func(m *Model, w, h int) { m.findingsPage.SetSize(w, h) },
|
||||
},
|
||||
{
|
||||
id: pageDocs,
|
||||
icon: func() string { return icons.I.Docs },
|
||||
|
||||
render: func(m *Model) string { return m.docs.View().Content },
|
||||
update: func(m *Model, msg tea.Msg) tea.Cmd {
|
||||
updated, cmd := m.docs.Update(msg)
|
||||
m.docs = updated.(docsUI.Model)
|
||||
return cmd
|
||||
},
|
||||
resize: func(m *Model, w, h int) { m.docs.SetSize(w, h) },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"charm.land/lipgloss/v2"
|
||||
"github.com/anotherhadi/spilltea/internal/style"
|
||||
)
|
||||
|
||||
type sidebarState string
|
||||
|
||||
const (
|
||||
sidebarHidden sidebarState = "hidden"
|
||||
sidebarCollapsed sidebarState = "collapsed"
|
||||
sidebarExpanded sidebarState = "expanded"
|
||||
)
|
||||
|
||||
func (m *Model) cycleSidebarState() {
|
||||
switch m.sidebarState {
|
||||
case sidebarHidden:
|
||||
m.sidebarState = sidebarCollapsed
|
||||
case sidebarCollapsed:
|
||||
m.sidebarState = sidebarExpanded
|
||||
default:
|
||||
m.sidebarState = sidebarHidden
|
||||
}
|
||||
}
|
||||
|
||||
func (m Model) getSidebarWidth() int {
|
||||
switch m.sidebarState {
|
||||
case sidebarHidden:
|
||||
return 0
|
||||
case sidebarCollapsed:
|
||||
return 8
|
||||
default:
|
||||
return 18
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) renderSidebar() string {
|
||||
if m.sidebarState == sidebarHidden {
|
||||
return ""
|
||||
}
|
||||
s := style.S
|
||||
// content width inside bordered panel
|
||||
inner := m.getSidebarWidth() - 2
|
||||
|
||||
titleText := "SPILLTEA"
|
||||
if m.sidebarState == sidebarCollapsed {
|
||||
titleText = "SPLT"
|
||||
}
|
||||
title := lipgloss.NewStyle().Width(inner).Bold(true).Foreground(s.Primary).Padding(0, 1).Render(titleText)
|
||||
divider := strings.Repeat("─", inner)
|
||||
|
||||
badgeSelected := lipgloss.NewStyle().Foreground(s.Primary).Bold(true)
|
||||
badgeNormal := lipgloss.NewStyle().Foreground(s.Subtle)
|
||||
textSelected := lipgloss.NewStyle().Foreground(s.Primary)
|
||||
textNormal := lipgloss.NewStyle().Foreground(s.Text)
|
||||
lineStyle := lipgloss.NewStyle().Width(inner).Padding(0, 1)
|
||||
|
||||
var items strings.Builder
|
||||
for i, entry := range sidebarEntries {
|
||||
selected := entry.id == m.page
|
||||
badgeStyle, textStyle := badgeNormal, textNormal
|
||||
if selected {
|
||||
badgeStyle, textStyle = badgeSelected, textSelected
|
||||
}
|
||||
icon := ""
|
||||
if entry.icon != nil {
|
||||
icon = entry.icon()
|
||||
}
|
||||
label := " " + icon
|
||||
if m.sidebarState != sidebarCollapsed {
|
||||
label += string(entry.id)
|
||||
}
|
||||
line := lineStyle.Render(badgeStyle.Render(strconv.Itoa(i+1)) + textStyle.Render(label))
|
||||
items.WriteString(line + "\n")
|
||||
}
|
||||
|
||||
body := lipgloss.JoinVertical(lipgloss.Left,
|
||||
title,
|
||||
lipgloss.NewStyle().Foreground(s.Subtle).Render(divider),
|
||||
items.String(),
|
||||
)
|
||||
|
||||
return s.Panel.Width(m.getSidebarWidth()).Height(m.height).Render(body)
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"charm.land/bubbles/v2/key"
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"github.com/anotherhadi/spilltea/internal/config"
|
||||
"github.com/anotherhadi/spilltea/internal/intercept"
|
||||
"github.com/anotherhadi/spilltea/internal/keys"
|
||||
"github.com/anotherhadi/spilltea/internal/plugins"
|
||||
proxyPkg "github.com/anotherhadi/spilltea/internal/proxy"
|
||||
copyasUI "github.com/anotherhadi/spilltea/internal/ui/components/copyas"
|
||||
notificationsUI "github.com/anotherhadi/spilltea/internal/ui/components/notifications"
|
||||
diffUI "github.com/anotherhadi/spilltea/internal/ui/diff"
|
||||
findingsUI "github.com/anotherhadi/spilltea/internal/ui/findings"
|
||||
historyUI "github.com/anotherhadi/spilltea/internal/ui/history"
|
||||
interceptUI "github.com/anotherhadi/spilltea/internal/ui/intercept"
|
||||
replayUI "github.com/anotherhadi/spilltea/internal/ui/replay"
|
||||
scopeUI "github.com/anotherhadi/spilltea/internal/ui/scope"
|
||||
)
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// Broker messages must always re-register their watchers
|
||||
switch msg := msg.(type) {
|
||||
case notificationsUI.NotificationMsg:
|
||||
var cmd tea.Cmd
|
||||
m.notifications, cmd = m.notifications.Update(msg)
|
||||
return m, cmd
|
||||
case notificationsUI.DismissMsg:
|
||||
var cmd tea.Cmd
|
||||
m.notifications, cmd = m.notifications.Update(msg)
|
||||
return m, cmd
|
||||
case intercept.RequestArrivedMsg:
|
||||
updated, cmd := m.intercept.Update(msg)
|
||||
m.intercept = updated.(interceptUI.Model)
|
||||
return m, tea.Batch(cmd, intercept.WaitForRequest(m.broker))
|
||||
case intercept.ResponseArrivedMsg:
|
||||
updated, cmd := m.intercept.Update(msg)
|
||||
m.intercept = updated.(interceptUI.Model)
|
||||
return m, tea.Batch(cmd, intercept.WaitForResponse(m.broker))
|
||||
|
||||
case plugins.PluginNotifMsg:
|
||||
cmd := plugins.WaitForNotif(m.pluginManager)
|
||||
notifCmd := func() tea.Msg {
|
||||
return notificationsUI.NotificationMsg{
|
||||
Title: msg.Title,
|
||||
Body: msg.Body,
|
||||
Kind: notificationsUI.KindInfo,
|
||||
}
|
||||
}
|
||||
return m, tea.Batch(cmd, notifCmd)
|
||||
|
||||
case plugins.PluginQuitMsg:
|
||||
log.Printf("plugin quit: %s", msg.Reason)
|
||||
m.pluginManager.RunOnQuit()
|
||||
return m, tea.Quit
|
||||
}
|
||||
|
||||
if m.copyAs.IsOpen() {
|
||||
if ws, ok := msg.(tea.WindowSizeMsg); ok {
|
||||
m.width = ws.Width
|
||||
m.height = ws.Height
|
||||
m.copyAs.SetSize(ws.Width, ws.Height)
|
||||
m.resizeChildren()
|
||||
return m, nil
|
||||
}
|
||||
var cmd tea.Cmd
|
||||
m.copyAs, cmd = m.copyAs.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
m.resizeChildren()
|
||||
|
||||
case scopeUI.ScopeChangedMsg:
|
||||
m.broker.SetScope(msg.Whitelist, msg.Blacklist)
|
||||
if m.database != nil {
|
||||
if err := m.database.SaveScope(msg.Whitelist, msg.Blacklist); err != nil {
|
||||
log.Printf("failed to persist scope: %v", err)
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
|
||||
case proxyPkg.ErrMsg:
|
||||
if msg.Err != nil {
|
||||
log.Printf("proxy error: %v", msg.Err)
|
||||
}
|
||||
return m, nil
|
||||
|
||||
case tickMsg:
|
||||
var cmds []tea.Cmd
|
||||
cmds = append(cmds, tickCmd())
|
||||
if m.page == pageHistory {
|
||||
cmds = append(cmds, m.history.RefreshCmd())
|
||||
}
|
||||
cmds = append(cmds, findingsUI.RefreshCmd(m.database))
|
||||
return m, tea.Batch(cmds...)
|
||||
|
||||
case findingsUI.FindingsLoadedMsg:
|
||||
updated, cmd := m.findingsPage.Update(msg)
|
||||
m.findingsPage = updated.(findingsUI.Model)
|
||||
return m, cmd
|
||||
|
||||
case replayUI.SendToReplayMsg:
|
||||
updated, cmd := m.replay.Update(msg)
|
||||
m.replay = updated.(replayUI.Model)
|
||||
if config.Global.Replay.SwitchToPageOnSend {
|
||||
m.page = pageReplay
|
||||
m.resizeChildren()
|
||||
} else {
|
||||
return m, tea.Batch(cmd, func() tea.Msg {
|
||||
return notificationsUI.NotificationMsg{
|
||||
Title: "Replay",
|
||||
Body: "Request queued in replay",
|
||||
Kind: notificationsUI.KindInfo,
|
||||
}
|
||||
})
|
||||
}
|
||||
return m, cmd
|
||||
|
||||
case diffUI.SendToDiffMsg:
|
||||
updated, cmd := m.diff.Update(msg)
|
||||
m.diff = updated.(diffUI.Model)
|
||||
return m, cmd
|
||||
|
||||
case diffUI.DiffReadyMsg:
|
||||
m.page = pageDiff
|
||||
m.resizeChildren()
|
||||
return m, nil
|
||||
|
||||
case historyUI.EntriesLoadedMsg:
|
||||
updated, cmd := m.history.Update(msg)
|
||||
m.history = updated.(historyUI.Model)
|
||||
return m, cmd
|
||||
|
||||
case tea.KeyPressMsg:
|
||||
// ctrl+c always quits, even when a textarea is focused.
|
||||
if msg.String() == "ctrl+c" {
|
||||
m.pluginManager.RunOnQuit()
|
||||
return m, tea.Quit
|
||||
}
|
||||
if key.Matches(msg, keys.Keys.Global.Quit) && !m.activeIsEditing() {
|
||||
m.pluginManager.RunOnQuit()
|
||||
return m, tea.Quit
|
||||
}
|
||||
|
||||
if key.Matches(msg, keys.Keys.Global.OpenLogs) {
|
||||
editor := os.Getenv("EDITOR")
|
||||
if editor == "" {
|
||||
editor = "vi"
|
||||
}
|
||||
logPath := filepath.Join(filepath.Dir(m.projectPath), "logs.log")
|
||||
return m, tea.ExecProcess(exec.Command(editor, logPath), nil)
|
||||
}
|
||||
|
||||
if !m.activeIsEditing() {
|
||||
switch {
|
||||
case key.Matches(msg, keys.Keys.Global.CopyRequest):
|
||||
if m.page == pageDiff {
|
||||
if raw := m.diff.CurrentRaw(); raw != "" {
|
||||
m.copyAs.SetSize(m.width, m.height)
|
||||
m.copyAs.Open(copyasUI.OpenMsg{
|
||||
RawRequest: raw,
|
||||
Scheme: "https",
|
||||
})
|
||||
}
|
||||
} else if m.page == pageIntercept {
|
||||
if raw := m.intercept.CurrentRaw(); raw != "" {
|
||||
m.copyAs.SetSize(m.width, m.height)
|
||||
m.copyAs.Open(copyasUI.OpenMsg{
|
||||
RawRequest: raw,
|
||||
Scheme: m.intercept.CurrentScheme(),
|
||||
})
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
|
||||
case key.Matches(msg, keys.Keys.Global.ToggleSidebar):
|
||||
m.cycleSidebarState()
|
||||
m.resizeChildren()
|
||||
|
||||
default:
|
||||
if p, ok := pageShortcuts[msg.String()]; ok {
|
||||
prev := m.page
|
||||
m.page = p
|
||||
if p == pageHistory && prev != pageHistory {
|
||||
return m, m.history.RefreshCmd()
|
||||
}
|
||||
if p == pageFindings {
|
||||
return m, findingsUI.RefreshCmd(m.database)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
m, cmd = m.updateActivePage(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m Model) activeIsEditing() bool {
|
||||
for _, e := range pageRegistry {
|
||||
if e.id == m.page && e.isEditing != nil {
|
||||
return e.isEditing(&m)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m Model) updateActivePage(msg tea.Msg) (Model, tea.Cmd) {
|
||||
for _, e := range pageRegistry {
|
||||
if e.id == m.page && e.update != nil {
|
||||
cmd := e.update(&m, msg)
|
||||
return m, cmd
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *Model) resizeChildren() {
|
||||
sidebarW := m.getSidebarWidth()
|
||||
h := m.height
|
||||
for _, e := range pageRegistry {
|
||||
if e.resize == nil {
|
||||
continue
|
||||
}
|
||||
e.resize(m, m.width-sidebarW, h)
|
||||
}
|
||||
m.notifications.SetSize(m.width, m.height)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"charm.land/lipgloss/v2"
|
||||
"github.com/anotherhadi/spilltea/internal/style"
|
||||
)
|
||||
|
||||
func (m Model) View() tea.View {
|
||||
if m.width == 0 {
|
||||
v := tea.NewView("")
|
||||
v.AltScreen = true
|
||||
return v
|
||||
}
|
||||
|
||||
normal := m.renderNormal()
|
||||
|
||||
if m.copyAs.IsOpen() {
|
||||
v := tea.NewView(m.copyAs.View(normal))
|
||||
v.AltScreen = true
|
||||
v.MouseMode = tea.MouseModeCellMotion
|
||||
return v
|
||||
}
|
||||
|
||||
rendered := normal
|
||||
if m.notifications.HasNotifications() {
|
||||
rendered = m.notifications.View(normal)
|
||||
}
|
||||
|
||||
v := tea.NewView(rendered)
|
||||
v.AltScreen = true
|
||||
v.MouseMode = tea.MouseModeCellMotion
|
||||
return v
|
||||
}
|
||||
|
||||
func (m Model) renderNormal() string {
|
||||
sidebar := m.renderSidebar()
|
||||
content := m.renderActivePage()
|
||||
return lipgloss.JoinHorizontal(lipgloss.Top, sidebar, content)
|
||||
}
|
||||
|
||||
func (m *Model) renderActivePage() string {
|
||||
for _, e := range pageRegistry {
|
||||
if e.id == m.page && e.render != nil {
|
||||
return e.render(m)
|
||||
}
|
||||
}
|
||||
return style.S.Faint.Render("Work in progress")
|
||||
}
|
||||
Reference in New Issue
Block a user