Revert "Add TUI foundation (Phase 1: Harness Foundation)"
This reverts commit 0ae44e9a64.
This commit is contained in:
parent
0ae44e9a64
commit
795d00302e
23
go.mod
23
go.mod
|
|
@ -10,34 +10,11 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/charmbracelet/bubbles v1.0.0 // indirect
|
||||
github.com/charmbracelet/bubbletea v1.3.10 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.1 // indirect
|
||||
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.6 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.9.0 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
|
||||
github.com/danieljoos/wincred v1.2.3 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/godbus/dbus/v5 v5.2.2 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.3.8 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
)
|
||||
|
|
|
|||
48
go.sum
48
go.sum
|
|
@ -1,62 +1,20 @@
|
|||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=
|
||||
github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
|
||||
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
|
||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
|
||||
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
|
||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||
github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=
|
||||
github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=
|
||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ=
|
||||
github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
|
||||
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
|
|
@ -69,19 +27,13 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
|||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/zalando/go-keyring v0.2.8 h1:6sD/Ucpl7jNq10rM2pgqTs0sZ9V3qMrqfIIy5YPccHs=
|
||||
github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cmakZDO5QGii0=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
||||
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
|||
27
main.go
27
main.go
|
|
@ -1,39 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/cmd"
|
||||
"github.com/gitlink-org/gitlink-cli/tui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 && os.Args[1] == "tui" {
|
||||
// TUI mode
|
||||
f, err := tea.LogToFile("gitlink-tui.log", "")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to init log: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
app := tui.NewApp()
|
||||
program := tea.NewProgram(
|
||||
app,
|
||||
tea.WithAltScreen(),
|
||||
tea.WithMouseCellMotion(),
|
||||
)
|
||||
if _, err := program.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CLI mode
|
||||
if err := cmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
|
|||
347
tui/app.go
347
tui/app.go
|
|
@ -1,347 +0,0 @@
|
|||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/tui/navigation"
|
||||
"github.com/gitlink-org/gitlink-cli/tui/store"
|
||||
"github.com/gitlink-org/gitlink-cli/tui/ui"
|
||||
"github.com/gitlink-org/gitlink-cli/tui/views"
|
||||
)
|
||||
|
||||
// View enum
|
||||
type View int
|
||||
|
||||
const (
|
||||
ViewHome View = iota
|
||||
ViewCode
|
||||
ViewIssueBoard
|
||||
ViewIssueDetail
|
||||
ViewPRList
|
||||
ViewPRDetail
|
||||
ViewCIDashboard
|
||||
ViewCILog
|
||||
ViewReleaseList
|
||||
ViewReleaseCreate
|
||||
ViewPMDashboard
|
||||
ViewExecutionDashboard
|
||||
ViewSearch
|
||||
ViewSettings
|
||||
)
|
||||
|
||||
// AppModel is the root Bubble Tea model.
|
||||
type AppModel struct {
|
||||
// Navigation
|
||||
currentView View
|
||||
router *navigation.Router
|
||||
|
||||
// Sub-models for each view
|
||||
home *views.HomeModel
|
||||
codeView tea.Model
|
||||
issueBoard tea.Model
|
||||
issueDetail tea.Model
|
||||
prList tea.Model
|
||||
prDetail tea.Model
|
||||
ciDashboard tea.Model
|
||||
ciLog tea.Model
|
||||
releaseList tea.Model
|
||||
releaseCreate tea.Model
|
||||
pmDashboard tea.Model
|
||||
execDashboard tea.Model
|
||||
searchView tea.Model
|
||||
settingsView tea.Model
|
||||
|
||||
// Layout
|
||||
width int
|
||||
height int
|
||||
ready bool
|
||||
|
||||
// Panels
|
||||
statusBar *ui.StatusBarModel
|
||||
tabBar *ui.TabBarModel
|
||||
aiPanel tea.Model // nil = not created yet (Phase 3)
|
||||
aiOpen bool
|
||||
|
||||
// Shared state
|
||||
store *store.Store
|
||||
|
||||
// Key bindings
|
||||
keyMap ui.KeyMap
|
||||
|
||||
// Notifications
|
||||
notifications []string
|
||||
}
|
||||
|
||||
// tabs defines the global tab bar.
|
||||
var tabs = []ui.TabDef{
|
||||
{Key: "1", Label: "Home"},
|
||||
{Key: "2", Label: "Code"},
|
||||
{Key: "3", Label: "Issues"},
|
||||
{Key: "4", Label: "PRs"},
|
||||
{Key: "5", Label: "CI"},
|
||||
{Key: "6", Label: "Release"},
|
||||
{Key: "7", Label: "PM"},
|
||||
}
|
||||
|
||||
func NewApp() *AppModel {
|
||||
s := store.New()
|
||||
|
||||
statusBar := ui.NewStatusBar()
|
||||
tabBar := ui.NewTabBar(tabs)
|
||||
|
||||
return &AppModel{
|
||||
currentView: ViewHome,
|
||||
router: navigation.NewRouter(),
|
||||
store: s,
|
||||
statusBar: statusBar,
|
||||
tabBar: tabBar,
|
||||
keyMap: ui.DefaultKeyMap,
|
||||
home: views.NewHomeModel(s),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *AppModel) Init() tea.Cmd {
|
||||
// Load auth status and current project info
|
||||
return tea.Batch(
|
||||
m.store.LoadAuthStatus,
|
||||
m.store.LoadProjectContext,
|
||||
)
|
||||
}
|
||||
|
||||
func (m *AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmds []tea.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
if !m.ready {
|
||||
m.ready = true
|
||||
}
|
||||
return m, nil
|
||||
|
||||
case tea.KeyMsg:
|
||||
// Global keybindings take priority over view-specific ones
|
||||
cmd := m.handleGlobalKeys(msg)
|
||||
if cmd != nil {
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
case store.AuthStatusMsg:
|
||||
// Update statusbar with auth info
|
||||
_ = msg
|
||||
case store.ProjectContextMsg:
|
||||
// Update statusbar with project info
|
||||
m.statusBar.SetProject(msg.Owner + "/" + msg.Repo)
|
||||
}
|
||||
cmds = nil
|
||||
|
||||
// Delegate to active view
|
||||
viewModel := m.currentViewModel()
|
||||
if viewModel != nil {
|
||||
newModel, cmd := viewModel.Update(msg)
|
||||
// If the model returned a different model (unlikely but handle)
|
||||
_ = newModel
|
||||
if cmd != nil {
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m *AppModel) View() string {
|
||||
if !m.ready {
|
||||
return "Initializing..."
|
||||
}
|
||||
|
||||
// Compute layout dimensions
|
||||
statusHeight := 1
|
||||
tabHeight := 1
|
||||
|
||||
mainHeight := m.height - statusHeight - tabHeight
|
||||
if mainHeight < 0 {
|
||||
mainHeight = 0
|
||||
}
|
||||
|
||||
mainWidth := m.width
|
||||
aiWidth := 0
|
||||
if m.aiOpen && m.aiPanel != nil {
|
||||
aiWidth = min(40, m.width*30/100)
|
||||
mainWidth = m.width - aiWidth
|
||||
}
|
||||
|
||||
// Render active view
|
||||
var mainContent string
|
||||
viewModel := m.currentViewModel()
|
||||
if viewModel != nil {
|
||||
mainContent = viewModel.View()
|
||||
}
|
||||
mainBox := lipgloss.NewStyle().
|
||||
Width(mainWidth).
|
||||
Height(mainHeight).
|
||||
Render(mainContent)
|
||||
|
||||
// AI panel placeholder
|
||||
var aiContent string
|
||||
if m.aiOpen {
|
||||
aiContent = lipgloss.NewStyle().
|
||||
Width(aiWidth).
|
||||
Height(mainHeight).
|
||||
BorderLeft(true).
|
||||
BorderStyle(lipgloss.NormalBorder()).
|
||||
Padding(0, 1).
|
||||
Render("AI Panel\n───\nCtrl+Space to toggle")
|
||||
}
|
||||
|
||||
// Combine main + AI
|
||||
topSection := lipgloss.JoinHorizontal(lipgloss.Top, mainBox, aiContent)
|
||||
|
||||
// Tab bar
|
||||
var tabBarStr string
|
||||
if !m.aiOpen || aiWidth == 0 {
|
||||
// Highlight active tab
|
||||
tabBarStr = m.tabBar.View(int(m.currentView))
|
||||
} else {
|
||||
tabBarStr = m.tabBar.View(int(m.currentView))
|
||||
}
|
||||
|
||||
// Status bar
|
||||
statusStr := m.statusBar.View()
|
||||
|
||||
return lipgloss.JoinVertical(
|
||||
lipgloss.Left,
|
||||
topSection,
|
||||
tabBarStr,
|
||||
statusStr,
|
||||
)
|
||||
}
|
||||
|
||||
func (m *AppModel) handleGlobalKeys(msg tea.KeyMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "ctrl+c":
|
||||
return tea.Quit
|
||||
|
||||
case "ctrl+space":
|
||||
m.aiOpen = !m.aiOpen
|
||||
return nil
|
||||
|
||||
case "ctrl+p":
|
||||
// Command palette (Phase 5, placeholder)
|
||||
m.notifications = append(m.notifications, "Command palette coming in Phase 5")
|
||||
return nil
|
||||
|
||||
case "?":
|
||||
// Help overlay (placeholder)
|
||||
m.notifications = append(m.notifications, "Help: j/k navigate, Enter select, Esc back, Ctrl+A AI actions")
|
||||
return nil
|
||||
|
||||
case "ctrl+a":
|
||||
// AI context menu (Phase 3, placeholder)
|
||||
m.notifications = append(m.notifications, fmt.Sprintf("AI menu for: %s", m.viewName()))
|
||||
return nil
|
||||
|
||||
case "1", "2", "3", "4", "5", "6", "7":
|
||||
return m.switchToTab(msg.String())
|
||||
|
||||
case "tab":
|
||||
// Cycle through views
|
||||
next := (int(m.currentView) + 1) % len(tabs)
|
||||
m.currentView = View(next)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delegate to view
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AppModel) switchToTab(key string) tea.Cmd {
|
||||
switch key {
|
||||
case "1":
|
||||
m.currentView = ViewHome
|
||||
case "2":
|
||||
m.currentView = ViewCode
|
||||
case "3":
|
||||
m.currentView = ViewIssueBoard
|
||||
case "4":
|
||||
m.currentView = ViewPRList
|
||||
case "5":
|
||||
m.currentView = ViewCIDashboard
|
||||
case "6":
|
||||
m.currentView = ViewReleaseList
|
||||
case "7":
|
||||
m.currentView = ViewPMDashboard
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AppModel) currentViewModel() tea.Model {
|
||||
switch m.currentView {
|
||||
case ViewHome:
|
||||
return m.home
|
||||
case ViewCode:
|
||||
return m.codeView
|
||||
case ViewIssueBoard:
|
||||
return m.issueBoard
|
||||
case ViewIssueDetail:
|
||||
return m.issueDetail
|
||||
case ViewPRList:
|
||||
return m.prList
|
||||
case ViewPRDetail:
|
||||
return m.prDetail
|
||||
case ViewCIDashboard:
|
||||
return m.ciDashboard
|
||||
case ViewCILog:
|
||||
return m.ciLog
|
||||
case ViewReleaseList:
|
||||
return m.releaseList
|
||||
case ViewReleaseCreate:
|
||||
return m.releaseCreate
|
||||
case ViewPMDashboard:
|
||||
return m.pmDashboard
|
||||
case ViewExecutionDashboard:
|
||||
return m.execDashboard
|
||||
case ViewSearch:
|
||||
return m.searchView
|
||||
case ViewSettings:
|
||||
return m.settingsView
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AppModel) viewName() string {
|
||||
names := map[View]string{
|
||||
ViewHome: "Home",
|
||||
ViewCode: "Code",
|
||||
ViewIssueBoard: "Issues",
|
||||
ViewIssueDetail: "Issue Detail",
|
||||
ViewPRList: "PRs",
|
||||
ViewPRDetail: "PR Detail",
|
||||
ViewCIDashboard: "CI",
|
||||
ViewCILog: "CI Log",
|
||||
ViewReleaseList: "Releases",
|
||||
ViewReleaseCreate: "New Release",
|
||||
ViewPMDashboard: "PM",
|
||||
ViewExecutionDashboard: "Execution",
|
||||
ViewSearch: "Search",
|
||||
ViewSettings: "Settings",
|
||||
}
|
||||
if n, ok := names[m.currentView]; ok {
|
||||
return n
|
||||
}
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
func (m *AppModel) navigate(to View) {
|
||||
m.router.Push(int(m.currentView))
|
||||
m.currentView = to
|
||||
}
|
||||
|
||||
func (m *AppModel) goBack() {
|
||||
if prev, ok := m.router.Pop(); ok {
|
||||
m.currentView = View(prev)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
package navigation
|
||||
|
||||
// Router provides a stack-based view history for back navigation.
|
||||
type Router struct {
|
||||
stack []int
|
||||
params map[int]map[string]interface{} // per-view params
|
||||
}
|
||||
|
||||
func NewRouter() *Router {
|
||||
return &Router{
|
||||
stack: make([]int, 0, 16),
|
||||
params: make(map[int]map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Push saves the current view index for later back navigation.
|
||||
func (r *Router) Push(view int) {
|
||||
r.stack = append(r.stack, view)
|
||||
}
|
||||
|
||||
// Pop returns the previous view, or false if the stack is empty.
|
||||
func (r *Router) Pop() (int, bool) {
|
||||
if len(r.stack) == 0 {
|
||||
return 0, false
|
||||
}
|
||||
prev := r.stack[len(r.stack)-1]
|
||||
r.stack = r.stack[:len(r.stack)-1]
|
||||
return prev, true
|
||||
}
|
||||
|
||||
// Depth returns the current stack depth.
|
||||
func (r *Router) Depth() int {
|
||||
return len(r.stack)
|
||||
}
|
||||
|
||||
// SetParam sets a parameter for a view.
|
||||
func (r *Router) SetParam(view int, key string, value interface{}) {
|
||||
if r.params[view] == nil {
|
||||
r.params[view] = make(map[string]interface{})
|
||||
}
|
||||
r.params[view][key] = value
|
||||
}
|
||||
|
||||
// GetParam gets a parameter for a view.
|
||||
func (r *Router) GetParam(view int, key string) interface{} {
|
||||
if p := r.params[view]; p != nil {
|
||||
return p[key]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearParams clears all params for a view.
|
||||
func (r *Router) ClearParams(view int) {
|
||||
delete(r.params, view)
|
||||
}
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/auth"
|
||||
"github.com/gitlink-org/gitlink-cli/internal/client"
|
||||
"github.com/gitlink-org/gitlink-cli/internal/config"
|
||||
"github.com/gitlink-org/gitlink-cli/internal/context"
|
||||
)
|
||||
|
||||
// Store is the central state container.
|
||||
type Store struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
// Client for GitLink API calls
|
||||
Client *client.Client
|
||||
|
||||
// Config
|
||||
Config *config.Config
|
||||
|
||||
// Auth
|
||||
Token string
|
||||
LoggedIn bool
|
||||
User *UserInfo
|
||||
|
||||
// Project
|
||||
Owner string
|
||||
Repo string
|
||||
|
||||
// Subscribers
|
||||
subs map[string][]chan tea.Msg
|
||||
}
|
||||
|
||||
// UserInfo holds basic authenticated user data.
|
||||
type UserInfo struct {
|
||||
Login string
|
||||
ID int
|
||||
Username string
|
||||
}
|
||||
|
||||
func New() *Store {
|
||||
cli, err := client.New()
|
||||
if err != nil {
|
||||
cli = nil
|
||||
}
|
||||
|
||||
return &Store{
|
||||
Client: cli,
|
||||
subs: make(map[string][]chan tea.Msg),
|
||||
}
|
||||
}
|
||||
|
||||
// LoadAuthStatus checks whether the user is authenticated.
|
||||
func (s *Store) LoadAuthStatus() tea.Msg {
|
||||
token, err := auth.LoadToken()
|
||||
if err != nil {
|
||||
return AuthStatusMsg{LoggedIn: false, Error: err}
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
s.Token = token
|
||||
s.LoggedIn = token != ""
|
||||
s.mu.Unlock()
|
||||
|
||||
return AuthStatusMsg{LoggedIn: token != ""}
|
||||
}
|
||||
|
||||
// LoadProjectContext detects the current GitLink project from git remote.
|
||||
func (s *Store) LoadProjectContext() tea.Msg {
|
||||
owner, repo, err := context.ResolveOwnerRepo("", "")
|
||||
if err != nil {
|
||||
return ProjectContextMsg{Error: err}
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
s.Owner = owner
|
||||
s.Repo = repo
|
||||
s.mu.Unlock()
|
||||
|
||||
return ProjectContextMsg{
|
||||
Owner: owner,
|
||||
Repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch sends an action to the store and publishes to subscribers.
|
||||
func (s *Store) Dispatch(action string, data interface{}) {
|
||||
s.mu.RLock()
|
||||
subs := s.subs[action]
|
||||
s.mu.RUnlock()
|
||||
|
||||
for _, ch := range subs {
|
||||
select {
|
||||
case ch <- StoreEvent{Action: action, Data: data}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe returns a channel for store events.
|
||||
func (s *Store) Subscribe(action string) chan tea.Msg {
|
||||
ch := make(chan tea.Msg, 10)
|
||||
s.mu.Lock()
|
||||
s.subs[action] = append(s.subs[action], ch)
|
||||
s.mu.Unlock()
|
||||
return ch
|
||||
}
|
||||
|
||||
// AuthStatusMsg is sent when auth status is loaded.
|
||||
type AuthStatusMsg struct {
|
||||
LoggedIn bool
|
||||
Error error
|
||||
}
|
||||
|
||||
// ProjectContextMsg is sent when project context is loaded.
|
||||
type ProjectContextMsg struct {
|
||||
Owner string
|
||||
Repo string
|
||||
Error error
|
||||
}
|
||||
|
||||
func (m ProjectContextMsg) String() string {
|
||||
if m.Error != nil {
|
||||
return fmt.Sprintf("No project (%v)", m.Error)
|
||||
}
|
||||
return m.Owner + "/" + m.Repo
|
||||
}
|
||||
|
||||
// StoreEvent is a generic store event.
|
||||
type StoreEvent struct {
|
||||
Action string
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
func (e StoreEvent) String() string {
|
||||
return fmt.Sprintf("[%s]", e.Action)
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// TabDef defines a single tab.
|
||||
type TabDef struct {
|
||||
Key string
|
||||
Label string
|
||||
}
|
||||
|
||||
// TabBarModel renders a horizontal tab bar.
|
||||
type TabBarModel struct {
|
||||
tabs []TabDef
|
||||
active int
|
||||
}
|
||||
|
||||
func NewTabBar(tabs []TabDef) *TabBarModel {
|
||||
return &TabBarModel{tabs: tabs}
|
||||
}
|
||||
|
||||
func (t *TabBarModel) View(active int) string {
|
||||
var items []string
|
||||
for i, tab := range t.tabs {
|
||||
label := fmt.Sprintf("[%s] %s", tab.Key, tab.Label)
|
||||
if i == active {
|
||||
label = StyleSelected.Render(" " + label + " ")
|
||||
} else {
|
||||
label = StyleMuted.Render(label)
|
||||
}
|
||||
items = append(items, label)
|
||||
}
|
||||
return lipgloss.JoinHorizontal(lipgloss.Left, items...)
|
||||
}
|
||||
|
||||
// StatusBarModel renders a bottom status bar.
|
||||
type StatusBarModel struct {
|
||||
project string
|
||||
message string
|
||||
authStatus string
|
||||
}
|
||||
|
||||
func NewStatusBar() *StatusBarModel {
|
||||
return &StatusBarModel{}
|
||||
}
|
||||
|
||||
func (s *StatusBarModel) SetProject(p string) {
|
||||
s.project = p
|
||||
}
|
||||
|
||||
func (s *StatusBarModel) SetMessage(msg string) {
|
||||
s.message = msg
|
||||
}
|
||||
|
||||
func (s *StatusBarModel) SetAuth(status string) {
|
||||
s.authStatus = status
|
||||
}
|
||||
|
||||
func (s *StatusBarModel) View() string {
|
||||
left := StyleMuted.Render(fmt.Sprintf(" Project: %s ", s.project))
|
||||
center := StyleCmd.Render(s.message)
|
||||
right := StyleMuted.Render(fmt.Sprintf(" Ctrl+A AI | Ctrl+Space Chat | Ctrl+P Cmd | ? Help "))
|
||||
|
||||
width := 80 // will be overridden by layout
|
||||
leftW := lipgloss.Width(left)
|
||||
rightW := lipgloss.Width(right)
|
||||
centerW := lipgloss.Width(center)
|
||||
|
||||
gapLeft := (width - leftW - centerW - rightW) / 2
|
||||
if gapLeft < 0 {
|
||||
gapLeft = 0
|
||||
}
|
||||
gapRight := width - leftW - centerW - rightW - gapLeft
|
||||
if gapRight < 0 {
|
||||
gapRight = 0
|
||||
}
|
||||
|
||||
bg := lipgloss.NewStyle().Background(lipgloss.Color("#313244")).Width(width)
|
||||
return bg.Render(left + strings.Repeat(" ", gapLeft) + center + strings.Repeat(" ", gapRight) + right)
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
package ui
|
||||
|
||||
import "github.com/charmbracelet/bubbles/key"
|
||||
|
||||
// KeyMap holds all global keybindings.
|
||||
type KeyMap struct {
|
||||
Quit key.Binding
|
||||
Help key.Binding
|
||||
AI key.Binding
|
||||
AIPanel key.Binding
|
||||
Commands key.Binding
|
||||
Refresh key.Binding
|
||||
|
||||
Up key.Binding
|
||||
Down key.Binding
|
||||
Back key.Binding
|
||||
Enter key.Binding
|
||||
|
||||
Create key.Binding
|
||||
Delete key.Binding
|
||||
Edit key.Binding
|
||||
Filter key.Binding
|
||||
}
|
||||
|
||||
var DefaultKeyMap = KeyMap{
|
||||
Quit: key.NewBinding(key.WithKeys("ctrl+c"), key.WithHelp("Ctrl+C", "Quit")),
|
||||
Help: key.NewBinding(key.WithKeys("?"), key.WithHelp("?", "Help")),
|
||||
AI: key.NewBinding(key.WithKeys("ctrl+a"), key.WithHelp("Ctrl+A", "AI actions")),
|
||||
AIPanel: key.NewBinding(key.WithKeys("ctrl+space"), key.WithHelp("Ctrl+Space", "AI Panel")),
|
||||
Commands: key.NewBinding(key.WithKeys("ctrl+p"), key.WithHelp("Ctrl+P", "Commands")),
|
||||
Refresh: key.NewBinding(key.WithKeys("r"), key.WithHelp("r", "Refresh")),
|
||||
|
||||
Up: key.NewBinding(key.WithKeys("up", "k"), key.WithHelp("j/k", "Navigate")),
|
||||
Down: key.NewBinding(key.WithKeys("down", "j"), key.WithHelp("", "")),
|
||||
Back: key.NewBinding(key.WithKeys("esc", "h"), key.WithHelp("Esc", "Back")),
|
||||
Enter: key.NewBinding(key.WithKeys("enter", "l"), key.WithHelp("Enter", "Select")),
|
||||
|
||||
Create: key.NewBinding(key.WithKeys("c"), key.WithHelp("c", "Create")),
|
||||
Delete: key.NewBinding(key.WithKeys("d"), key.WithHelp("d", "Delete")),
|
||||
Edit: key.NewBinding(key.WithKeys("e"), key.WithHelp("e", "Edit")),
|
||||
Filter: key.NewBinding(key.WithKeys("/"), key.WithHelp("/", "Filter")),
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
package ui
|
||||
|
||||
import "github.com/charmbracelet/lipgloss"
|
||||
|
||||
// Common styles used across components.
|
||||
var (
|
||||
StyleTitle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(DefaultTheme.Primary)
|
||||
|
||||
StyleMuted = lipgloss.NewStyle().
|
||||
Foreground(DefaultTheme.Muted)
|
||||
|
||||
StyleError = lipgloss.NewStyle().
|
||||
Foreground(DefaultTheme.Error)
|
||||
|
||||
StyleSuccess = lipgloss.NewStyle().
|
||||
Foreground(DefaultTheme.Success)
|
||||
|
||||
StyleWarning = lipgloss.NewStyle().
|
||||
Foreground(DefaultTheme.Warning)
|
||||
|
||||
StyleAccent = lipgloss.NewStyle().
|
||||
Foreground(DefaultTheme.Accent)
|
||||
|
||||
StyleBorder = lipgloss.RoundedBorder()
|
||||
|
||||
StyleSelected = lipgloss.NewStyle().
|
||||
Background(lipgloss.Color("#45475A"))
|
||||
|
||||
StyleCmd = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#A6E3A1"))
|
||||
)
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
package ui
|
||||
|
||||
import "github.com/charmbracelet/lipgloss"
|
||||
|
||||
// Theme defines the color palette.
|
||||
type Theme struct {
|
||||
Primary lipgloss.Color
|
||||
Success lipgloss.Color
|
||||
Warning lipgloss.Color
|
||||
Error lipgloss.Color
|
||||
Muted lipgloss.Color
|
||||
Background lipgloss.Color
|
||||
Foreground lipgloss.Color
|
||||
Border lipgloss.Color
|
||||
Accent lipgloss.Color
|
||||
}
|
||||
|
||||
var DefaultTheme = Theme{
|
||||
Primary: lipgloss.Color("#6C8EBF"),
|
||||
Success: lipgloss.Color("#82B366"),
|
||||
Warning: lipgloss.Color("#D6B656"),
|
||||
Error: lipgloss.Color("#B85450"),
|
||||
Muted: lipgloss.Color("#666666"),
|
||||
Background: lipgloss.Color("#1E1E2E"),
|
||||
Foreground: lipgloss.Color("#CDD6F4"),
|
||||
Border: lipgloss.Color("#45475A"),
|
||||
Accent: lipgloss.Color("#89B4FA"),
|
||||
}
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/tui/store"
|
||||
"github.com/gitlink-org/gitlink-cli/tui/ui"
|
||||
)
|
||||
|
||||
// HomeModel is the dashboard view.
|
||||
type HomeModel struct {
|
||||
store *store.Store
|
||||
width int
|
||||
height int
|
||||
|
||||
// Data
|
||||
project string
|
||||
authStatus string
|
||||
issuesCount string
|
||||
prsCount string
|
||||
ciStatus string
|
||||
releaseVer string
|
||||
|
||||
// Onboarding
|
||||
firstRun bool
|
||||
onboardIdx int
|
||||
}
|
||||
|
||||
func NewHomeModel(s *store.Store) *HomeModel {
|
||||
return &HomeModel{
|
||||
store: s,
|
||||
firstRun: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *HomeModel) Init() tea.Cmd {
|
||||
return tea.Batch(
|
||||
m.store.LoadAuthStatus,
|
||||
m.store.LoadProjectContext,
|
||||
)
|
||||
}
|
||||
|
||||
func (m *HomeModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
|
||||
case store.AuthStatusMsg:
|
||||
if msg.LoggedIn {
|
||||
m.authStatus = "Logged in"
|
||||
} else {
|
||||
m.authStatus = "Not logged in (run gitlink-cli auth login)"
|
||||
}
|
||||
|
||||
case store.ProjectContextMsg:
|
||||
if msg.Error != nil {
|
||||
m.project = "No GitLink project detected"
|
||||
} else {
|
||||
m.project = msg.Owner + "/" + msg.Repo
|
||||
}
|
||||
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "enter":
|
||||
if m.firstRun {
|
||||
m.firstRun = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *HomeModel) View() string {
|
||||
if m.firstRun {
|
||||
return m.onboardingView()
|
||||
}
|
||||
return m.dashboardView()
|
||||
}
|
||||
|
||||
func (m *HomeModel) dashboardView() string {
|
||||
title := ui.StyleTitle.Render("🏠 Home Dashboard")
|
||||
|
||||
content := lipgloss.JoinVertical(
|
||||
lipgloss.Left,
|
||||
title,
|
||||
"",
|
||||
fmt.Sprintf(" Project: %s", ui.StyleAccent.Render(m.project)),
|
||||
fmt.Sprintf(" Auth: %s", m.authStatus),
|
||||
"",
|
||||
ui.StyleMuted.Render(" Quick start:"),
|
||||
fmt.Sprintf(" %s Switch views (1-7)", ui.StyleCmd.Render("[1]-[7]")),
|
||||
fmt.Sprintf(" %s AI actions (context-aware)", ui.StyleCmd.Render("Ctrl+A")),
|
||||
fmt.Sprintf(" %s Open AI chat panel", ui.StyleCmd.Render("Ctrl+Space")),
|
||||
fmt.Sprintf(" %s Command palette", ui.StyleCmd.Render("Ctrl+P")),
|
||||
fmt.Sprintf(" %s Help", ui.StyleCmd.Render("?")),
|
||||
)
|
||||
|
||||
return lipgloss.NewStyle().
|
||||
Padding(1, 2).
|
||||
Render(content)
|
||||
}
|
||||
|
||||
func (m *HomeModel) onboardingView() string {
|
||||
welcome := lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(ui.DefaultTheme.Primary).
|
||||
Render("Welcome to GitLink TUI")
|
||||
|
||||
box := lipgloss.NewStyle().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(ui.DefaultTheme.Primary).
|
||||
Padding(1, 2).
|
||||
Width(60)
|
||||
|
||||
content := lipgloss.JoinVertical(
|
||||
lipgloss.Left,
|
||||
welcome,
|
||||
"",
|
||||
lipgloss.NewStyle().Bold(true).Render("One key to remember:"),
|
||||
"",
|
||||
fmt.Sprintf(" %s → AI actions, anywhere, context-aware", ui.StyleCmd.Render("Ctrl+A")),
|
||||
"",
|
||||
lipgloss.NewStyle().Bold(true).Render("Also useful:"),
|
||||
fmt.Sprintf(" %s AI Chat panel", ui.StyleCmd.Render("Ctrl+Space")),
|
||||
fmt.Sprintf(" %s Command palette", ui.StyleCmd.Render("Ctrl+P")),
|
||||
fmt.Sprintf(" %s Switch views", ui.StyleCmd.Render("[1]-[7]")),
|
||||
fmt.Sprintf(" %s Help", ui.StyleCmd.Render("?")),
|
||||
"",
|
||||
fmt.Sprintf(" Project: %s", ui.StyleAccent.Render(m.project)),
|
||||
fmt.Sprintf(" Auth: %s", m.authStatus),
|
||||
"",
|
||||
ui.StyleCmd.Render(" [Enter] Got it!"),
|
||||
)
|
||||
|
||||
return box.Render(content)
|
||||
}
|
||||
Loading…
Reference in New Issue