Functional, needs some polish

This commit is contained in:
2026-01-23 09:55:03 -06:00
parent fddf514853
commit 86ab7e55f3
7 changed files with 160 additions and 211 deletions

View File

@@ -49,7 +49,7 @@ type App struct {
func NewApp() *App {
a := &App{
style: tcell.StyleDefault.Foreground(tcell.ColorYellow),
style: tcell.StyleDefault.Foreground(tcell.ColorLime),
}
err := a.init()
cobra.CheckErr(err)

View File

@@ -29,7 +29,6 @@ import (
"git.bullercodeworks.com/brian/expds/data/models"
wd "git.bullercodeworks.com/brian/expds/widgets"
w "git.bullercodeworks.com/brian/tcell-widgets"
h "git.bullercodeworks.com/brian/tcell-widgets/helpers"
"github.com/bluesky-social/indigo/atproto/syntax"
"github.com/gdamore/tcell"
"github.com/spf13/viper"
@@ -48,7 +47,7 @@ type ScreenHome struct {
columns *w.LinearLayout
activePds *models.Pds
pdsListing *wd.SimpleListWithHelp
pdsListing *w.SimpleListWithHelp
jsonContent *wd.JsonContent
pdsListingTypes []models.EntryType
@@ -61,6 +60,11 @@ type ScreenHome struct {
cli *w.Cli
isLoading bool
loading *w.Spinner
loadingFrame *w.Text
pdsStatus *w.Text
cursor int
}
@@ -71,6 +75,18 @@ func (s *ScreenHome) Init(a *App) {
s.expandedEntries = make(map[string]interface{})
s.recordIdsToNSIDs = make(map[string]syntax.NSID)
s.isLoading = true
s.loadingFrame = w.NewText("home.loadingframe", s.style)
s.loadingFrame.SetText("┤ ├")
s.loadingFrame.SetPos(w.Coord{X: 1, Y: 1})
s.pdsStatus = w.NewText("home.pdsstatus", s.style.Foreground(tcell.ColorRed))
s.pdsStatus.SetText("𐄂")
s.pdsStatus.SetPos(w.Coord{X: 2, Y: 1})
s.loading = w.NewSpinner("home.loading", s.style)
s.loading.SetVisible(true)
s.loading.SetPos(w.Coord{X: 2, Y: 1})
s.menuLayout = w.NewTopMenuLayout("home.toplayout", s.style)
s.initMenu()
@@ -87,16 +103,22 @@ func (s *ScreenHome) Init(a *App) {
s.columns = w.NewLinearLayout("home.layout.columns", s.style)
s.columns.SetOrientation(w.LinLayH)
s.pdsListing = wd.NewSimpleListWithHelp("pdslisting", s.style)
s.pdsListing.SetBorder(h.BRD_SIMPLE)
s.pdsListing.SetTitle("No PDS Loaded")
s.pdsListing = w.NewSimpleListWithHelp("pdslisting", s.style)
s.pdsListing.SetBorder(
[]rune{'─', '┬', '│', '┴', '─', '└', '│', '┌', '├', '─', '┤', '┬', '│', '┴', '┼'},
)
s.pdsListing.SetTitle("─── No PDS Loaded")
s.pdsListing.SetOnSelect(s.selectPdsListingEntry)
s.pdsListing.SetOnChange(s.updateJsonView)
s.pdsListing.SetVimMode(viper.GetBool(data.KeyVimMode))
s.jsonContent = wd.NewJsonContent("jsoncontent", s.style)
brd := w.NewBorderedWidget("jsoncontentbrd", s.style, s.jsonContent)
brd.SetBorder(
[]rune{'─', '┐', '│', '┘', '─', '─', ' ', '─', '├', '─', '┤', '┬', '│', '┴', '┼'},
)
s.columns.AddAll(s.pdsListing, s.jsonContent)
s.columns.AddAll(s.pdsListing, brd)
s.layout.AddAll(s.columns, s.cli)
s.layout.SetWeight(s.columns, 4)
@@ -120,8 +142,18 @@ func (s *ScreenHome) HandleKey(ev *tcell.EventKey) bool {
return s.menuLayout.HandleKey(ev)
}
func (s *ScreenHome) HandleTime(ev *tcell.EventTime) { s.menuLayout.HandleTime(ev) }
func (s *ScreenHome) Draw() { s.a.DrawWidget(s.menuLayout) }
func (s *ScreenHome) HandleTime(ev *tcell.EventTime) {
s.menuLayout.HandleTime(ev)
s.loading.HandleTime(ev)
}
func (s *ScreenHome) Draw() {
s.a.DrawWidget(s.menuLayout)
// These are outside of the menuLayout
//s.a.DrawWidget(s.loadingFrame)
//s.a.DrawWidget(s.loading)
//s.a.DrawWidget(s.pdsStatus)
}
func (s *ScreenHome) Exit() error { return nil }
func (s *ScreenHome) Log(t string, a ...any) {
s.cli.Log(t, a...)
@@ -130,16 +162,25 @@ func (s *ScreenHome) Log(t string, a ...any) {
func (s *ScreenHome) initMenu() {
s.menuLayout.SetActive(true)
menu := s.menuLayout.Menu()
var vimText = "Enable Vim Mode"
if viper.GetBool(data.KeyVimMode) {
vimText = "Disable Vim Mode"
}
s.menuLayout.AddMenuItems(
menu.CreateMenuItem("File", nil, 'f',
menu.CreateMenuItem("Exit", func() bool {
s.menuLayout.CreateMenuItem("file", "File", nil, 'f',
s.menuLayout.CreateMenuItem("file.reloadpds", "Reload PDS", func() bool {
if s.activePds == nil {
return false
}
return s.cliGetPds("getpds", s.activePds.AtId.String())
}, 'r'),
s.menuLayout.CreateMenuItem("file.exit", "Exit", func() bool {
s.a.Exit()
return true
}, 'x'),
),
menu.CreateMenuItem("Settings", nil, 's',
menu.CreateMenuItem("Toggle Vim Mode", func() bool {
s.menuLayout.CreateMenuItem("settings", "Settings", nil, 's',
s.menuLayout.CreateMenuItem("settings.vimmode", vimText, func() bool {
viper.Set(data.KeyVimMode, !viper.GetBool(data.KeyVimMode))
viper.WriteConfig()
s.update()
@@ -149,8 +190,42 @@ func (s *ScreenHome) initMenu() {
)
}
func (s *ScreenHome) setPdsStatusLoading() {
s.loading.SetVisible(true)
s.pdsStatus.SetVisible(false)
s.pdsStatus.SetStyle(s.style.Foreground(tcell.ColorLime))
s.pdsStatus.SetText(" ")
}
func (s *ScreenHome) setPdsStatusGood() {
s.loading.SetVisible(false)
s.pdsStatus.SetVisible(true)
s.pdsStatus.SetStyle(s.style.Foreground(tcell.ColorLime))
s.pdsStatus.SetText("✔")
}
func (s *ScreenHome) setPdsStatusBad() {
s.loading.SetVisible(false)
s.pdsStatus.SetVisible(true)
s.pdsStatus.SetStyle(s.style.Foreground(tcell.ColorRed))
s.pdsStatus.SetText("𐄂")
}
func (s *ScreenHome) update() {
s.pdsListing.SetVimMode(viper.GetBool(data.KeyVimMode))
vimMI := s.menuLayout.FindItem("settings.vimmode")
var vimText = "Enable Vim Mode"
if viper.GetBool(data.KeyVimMode) {
vimText = "Disable Vim Mode"
}
vimMI.SetLabel(vimText)
miReload := s.menuLayout.FindItem("file.reloadpds")
if s.activePds == nil {
miReload.SetDisabled(true)
miReload.SetStyle(s.style.Foreground(tcell.ColorGray))
} else {
miReload.SetDisabled(false)
miReload.SetStyle(s.style.Foreground(tcell.ColorLime))
}
}
func (s *ScreenHome) initCli() {
@@ -177,26 +252,31 @@ func (s *ScreenHome) showCli() {
}
func (s *ScreenHome) cliGetPds(args ...string) bool {
if len(args) == 0 {
s.isLoading = true
if len(args) < 1 {
s.Log("No id given.")
s.Log("Usage: 'getpds <atproto id>'")
s.isLoading = false
return true
}
go func() {
pds, err := s.r.GetPDS(args[1])
if err != nil {
s.cli.Log(err.Error())
return true
s.isLoading = false
}
s.cli.Log("Retrieved: %s (%s)", pds.AtId, pds.Did)
s.activePds = pds
s.expandedEntries = make(map[string]interface{})
s.updatePdsListing()
s.updateJsonView(s.pdsListing.SelectedIndex(), s.pdsListing.GetSelectedItem())
s.isLoading = false
}()
return true
}
func (s *ScreenHome) updatePdsListing() {
s.pdsListing.SetTitle(fmt.Sprintf("%s (%s)", s.activePds.AtId.String(), s.activePds.Did.String()))
s.pdsListing.SetTitle(fmt.Sprintf("─── %s (%s)", s.activePds.AtId.String(), s.activePds.Did.String()))
// When we first get the pds, all entries are models.TypeNSID
s.pdsListingTypes = []models.EntryType{}
s.recordIdsToNSIDs = make(map[string]syntax.NSID)

6
go.mod
View File

@@ -3,16 +3,17 @@ module git.bullercodeworks.com/brian/expds
go 1.25.1
require (
git.bullercodeworks.com/brian/tcell-widgets v0.2.1
github.com/bluesky-social/indigo v0.0.0-20260120225912-12d69fa4d209
github.com/gdamore/tcell v1.4.1
github.com/ipfs/go-cid v0.4.1
github.com/muesli/go-app-paths v0.2.2
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.21.0
)
require (
git.bullercodeworks.com/brian/tcell-widgets v0.0.0-20251115204352-197df3e02988 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bluesky-social/indigo v0.0.0-20260120225912-12d69fa4d209 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.3.1 // indirect
@@ -33,7 +34,6 @@ require (
github.com/ipfs/bbloom v0.0.4 // indirect
github.com/ipfs/go-block-format v0.2.0 // indirect
github.com/ipfs/go-blockservice v0.5.2 // indirect
github.com/ipfs/go-cid v0.4.1 // indirect
github.com/ipfs/go-datastore v0.6.0 // indirect
github.com/ipfs/go-ipfs-blockstore v1.3.1 // indirect
github.com/ipfs/go-ipfs-ds-help v1.1.1 // indirect

6
go.sum
View File

@@ -1,5 +1,11 @@
git.bullercodeworks.com/brian/tcell-widgets v0.0.0-20251115204352-197df3e02988 h1:O4cDOi2FcMIEKWFQ8KhlIpRfyxAZ2m1o8P6Y5B6uhNg=
git.bullercodeworks.com/brian/tcell-widgets v0.0.0-20251115204352-197df3e02988/go.mod h1:3TlKbuGjY8nrKL5Qcp28h+KnEsXBl3iCwACTy79bdPg=
git.bullercodeworks.com/brian/tcell-widgets v0.1.0 h1:FUQrnN/mqyb8MXvng9nYloG/4Mw50LYXgealkHe7mwE=
git.bullercodeworks.com/brian/tcell-widgets v0.1.0/go.mod h1:3TlKbuGjY8nrKL5Qcp28h+KnEsXBl3iCwACTy79bdPg=
git.bullercodeworks.com/brian/tcell-widgets v0.2.0 h1:f6ZYHdz/lZsIHf770dviuRenWAQ8FReKpJXTiVmc4oY=
git.bullercodeworks.com/brian/tcell-widgets v0.2.0/go.mod h1:3TlKbuGjY8nrKL5Qcp28h+KnEsXBl3iCwACTy79bdPg=
git.bullercodeworks.com/brian/tcell-widgets v0.2.1 h1:f7rvveTXf2U3XdTbL/1h3lSPt3NnKa7dCgzXYeMX/Go=
git.bullercodeworks.com/brian/tcell-widgets v0.2.1/go.mod h1:3TlKbuGjY8nrKL5Qcp28h+KnEsXBl3iCwACTy79bdPg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=

View File

@@ -86,6 +86,7 @@ func (w *JsonContent) Draw(screen tcell.Screen) {
}
}
func (w *JsonContent) SetStyle(s tcell.Style) { w.style = s }
func (w *JsonContent) Active() bool { return w.active }
func (w *JsonContent) SetActive(a bool) { w.active = a }
func (w *JsonContent) Visible() bool { return w.visible }

View File

@@ -1,139 +0,0 @@
/*
Copyright © Brian Buller <brian@bullercodeworks.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package widgets
import (
"fmt"
t "git.bullercodeworks.com/brian/tcell-widgets"
h "git.bullercodeworks.com/brian/tcell-widgets/helpers"
"github.com/gdamore/tcell"
)
type SimpleListWithHelp struct {
id string
style tcell.Style
x, y int
w, h int
visible bool
list *t.SimpleList
help *t.Text
}
func NewSimpleListWithHelp(id string, s tcell.Style) *SimpleListWithHelp {
ret := &SimpleListWithHelp{}
ret.Init(id, s)
return ret
}
func (w *SimpleListWithHelp) Init(id string, s tcell.Style) {
w.id = id
w.style = s
w.visible = true
w.list = t.NewSimpleList(fmt.Sprintf("%s.list", id), s)
w.help = t.NewText(fmt.Sprintf("%s.help", id), s)
}
func (w *SimpleListWithHelp) Id() string { return w.id }
func (w *SimpleListWithHelp) HandleResize(ev *tcell.EventResize) {
w.w, w.h = ev.Size()
hh := 0
if w.list.Active() {
hh = w.help.WantH()
}
w.help.HandleResize((t.Coord{X: w.w, Y: hh}).ResizeEvent())
w.help.SetPos(t.Coord{X: 1, Y: w.h - hh})
w.list.HandleResize((t.Coord{X: w.w, Y: w.h - hh}).ResizeEvent())
w.list.SetPos(t.Coord{X: 0, Y: 0})
}
func (w *SimpleListWithHelp) GetKeyMap() *t.KeyMap { return w.list.GetKeyMap() }
func (w *SimpleListWithHelp) SetKeyMap(km *t.KeyMap) { w.list.SetKeyMap(km) }
func (w *SimpleListWithHelp) HandleKey(ev *tcell.EventKey) bool { return w.list.HandleKey(ev) }
func (w *SimpleListWithHelp) HandleTime(ev *tcell.EventTime) {
w.list.HandleTime(ev)
w.help.HandleTime(ev)
}
func (w *SimpleListWithHelp) Draw(screen tcell.Screen) {
if !w.visible {
return
}
w.GetPos().DrawOffset(w.list, screen)
w.GetPos().DrawOffset(w.help, screen)
}
func (w *SimpleListWithHelp) Active() bool { return w.list.Active() }
func (w *SimpleListWithHelp) SetActive(a bool) {
w.list.SetActive(a)
w.help.SetVisible(a)
}
func (w *SimpleListWithHelp) Visible() bool { return w.visible }
func (w *SimpleListWithHelp) SetVisible(a bool) { w.visible = a }
func (w *SimpleListWithHelp) Focusable() bool { return w.list.Focusable() }
func (w *SimpleListWithHelp) SetFocusable(b bool) { w.list.SetFocusable(b) }
func (w *SimpleListWithHelp) SetX(x int) { w.x = x }
func (w *SimpleListWithHelp) SetY(y int) { w.y = y }
func (w *SimpleListWithHelp) GetX() int { return w.x }
func (w *SimpleListWithHelp) GetY() int { return w.y }
func (w *SimpleListWithHelp) GetPos() t.Coord { return t.Coord{X: w.x, Y: w.y} }
func (w *SimpleListWithHelp) SetPos(c t.Coord) { w.x, w.y = c.X, c.Y }
func (w *SimpleListWithHelp) GetW() int { return w.w }
func (w *SimpleListWithHelp) GetH() int { return w.h }
func (w *SimpleListWithHelp) SetW(wd int) { w.w = wd }
func (w *SimpleListWithHelp) SetH(h int) { w.h = h }
func (w *SimpleListWithHelp) SetSize(c t.Coord) { w.w, w.h = c.X, c.Y }
func (w *SimpleListWithHelp) WantW() int { return w.list.WantW() + w.help.WantW() }
func (w *SimpleListWithHelp) WantH() int { return h.Max(w.list.WantH(), w.help.WantH()) }
func (w *SimpleListWithHelp) MinW() int { return h.Max(w.list.MinW(), w.help.MinW()) }
func (w *SimpleListWithHelp) MinH() int { return w.list.MinH() + w.help.MinH() }
// Help Text Widget
func (w *SimpleListWithHelp) SetHelp(txt string) { w.help.SetText(txt) }
func (w *SimpleListWithHelp) GetHelp() string { return w.help.GetText() }
// SimpleList Widget
func (w *SimpleListWithHelp) SetCursorWrap(b bool) { w.list.SetCursorWrap(b) }
func (w *SimpleListWithHelp) MoveUp() bool { return w.list.MoveUp() }
func (w *SimpleListWithHelp) MoveDown() bool { return w.list.MoveDown() }
func (w *SimpleListWithHelp) SetTitle(ttl string) { w.list.SetTitle(ttl) }
func (w *SimpleListWithHelp) SetList(l []string) { w.list.SetList(l) }
func (w *SimpleListWithHelp) Clear() { w.list.Clear() }
func (w *SimpleListWithHelp) Add(l string) { w.list.Add(l) }
func (w *SimpleListWithHelp) Remove(l string) { w.list.Remove(l) }
func (w *SimpleListWithHelp) SetBorder(brd []rune) { w.list.SetBorder(brd) }
func (w *SimpleListWithHelp) SetItemStyle(idx int, s tcell.Style) { w.list.SetItemStyle(idx, s) }
func (w *SimpleListWithHelp) SetItem(idx int, txt string) { w.list.SetItem(idx, txt) }
func (w *SimpleListWithHelp) SetSelectedIndex(i int) { w.list.SetSelectedIndex(i) }
func (w *SimpleListWithHelp) SelectedIndex() int { return w.list.SelectedIndex() }
func (w *SimpleListWithHelp) ClearBorder() { w.list.ClearBorder() }
func (w *SimpleListWithHelp) SetOnSelect(s func(int, string) bool) { w.list.SetOnSelect(s) }
func (w *SimpleListWithHelp) SetVimMode(b bool) { w.list.SetVimMode(b) }
func (w *SimpleListWithHelp) SetLogger(l func(string, ...any)) { w.list.SetLogger(l) }
func (w *SimpleListWithHelp) Log(txt string, args ...any) { w.list.Log(txt, args...) }
func (w *SimpleListWithHelp) SetOnChange(c func(idx int, nm string) bool) { w.list.SetOnChange(c) }
func (w *SimpleListWithHelp) GetSelectedItem() string { return w.list.GetSelectedItem() }
func (w *SimpleListWithHelp) GetAllItems() []string { return w.list.GetAllItems() }
func (w *SimpleListWithHelp) GetAllItemStyles() map[int]tcell.Style { return w.list.GetAllItemStyles() }

View File

@@ -69,6 +69,7 @@ func (w *StatusBar) Draw(screen tcell.Screen) {
}
}
func (w *StatusBar) SetStyle(s tcell.Style) { w.style = s }
func (w *StatusBar) Active() bool { return false }
func (w *StatusBar) SetActive(a bool) {}
func (w *StatusBar) Visible() bool { return w.visible }