From 968b84999019f8609a6edaf851fc10015a8612a1 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 6 Dec 2022 21:59:29 -0500 Subject: [PATCH] feat(ui): display quick guide for empty repositories --- ui/components/statusbar/statusbar.go | 12 ++++++++ ui/pages/repo/empty.go | 45 ++++++++++++++++++++++++++++ ui/pages/repo/readme.go | 20 +++++++++---- ui/pages/repo/refs.go | 3 ++ ui/pages/repo/repo.go | 20 +++++++++---- ui/styles/styles.go | 7 +---- ui/ui.go | 21 +++++++------ 7 files changed, 100 insertions(+), 28 deletions(-) create mode 100644 ui/pages/repo/empty.go diff --git a/ui/components/statusbar/statusbar.go b/ui/components/statusbar/statusbar.go index 7b960569faf23f4668c2bfc3ddc3792cf73c9ad7..8d29a48e76aa4d1766f33c45ddb8d14246574810 100644 --- a/ui/components/statusbar/statusbar.go +++ b/ui/components/statusbar/statusbar.go @@ -86,3 +86,15 @@ func (s *StatusBar) View() string { ), ) } + +// StatusBarCmd returns a command that sets the status bar information. +func StatusBarCmd(key, value, info, branch string) tea.Cmd { + return func() tea.Msg { + return StatusBarMsg{ + Key: key, + Value: value, + Info: info, + Branch: branch, + } + } +} diff --git a/ui/pages/repo/empty.go b/ui/pages/repo/empty.go new file mode 100644 index 0000000000000000000000000000000000000000..1bafffe71dd493f2442ada22e97ed7165c36f423 --- /dev/null +++ b/ui/pages/repo/empty.go @@ -0,0 +1,45 @@ +package repo + +import ( + "fmt" + "strings" + + "github.com/charmbracelet/soft-serve/server/config" +) + +func defaultEmptyRepoMsg(cfg *config.Config, repo string) string { + host := cfg.Host + if cfg.SSH.Port != 22 { + host = fmt.Sprintf("%s:%d", host, cfg.SSH.Port) + } + repo = strings.TrimSuffix(repo, ".git") + return fmt.Sprintf(`# Quick Start + +Get started by cloning this repository, add your files, commit, and push. + +## Clone this repository. + +`+"```"+`sh +git clone ssh://%[1]s/%[2]s.git +`+"```"+` + +## Creating a new repository on the command line + +`+"```"+`sh +touch README.md +git init +git add README.md +git branch -M main +git commit -m "first commit" +git remote add origin ssh://%[1]s/%[2]s.git +git push -u origin main +`+"```"+` + +## Pushing an existing repository from the command line + +`+"```"+`sh +git remote add origin ssh://%[1]s/%[2]s.git +git push -u origin main +`+"```"+` +`, host, repo) +} diff --git a/ui/pages/repo/readme.go b/ui/pages/repo/readme.go index 405b6c43917d8c0e0a5532125513f488d73b527f..10ced1af025b83a0bb6b2bfa831d41b286fa7f7b 100644 --- a/ui/pages/repo/readme.go +++ b/ui/pages/repo/readme.go @@ -2,6 +2,7 @@ package repo import ( "fmt" + "path/filepath" "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" @@ -17,10 +18,11 @@ type ReadmeMsg struct { // Readme is the readme component page. type Readme struct { - common common.Common - code *code.Code - ref RefMsg - repo *git.Repository + common common.Common + code *code.Code + ref RefMsg + repo *git.Repository + readmePath string } // NewReadme creates a new readme model. @@ -79,6 +81,9 @@ func (r *Readme) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case RefMsg: r.ref = msg cmds = append(cmds, r.Init()) + case EmptyRepoMsg: + r.code.SetContent(defaultEmptyRepoMsg(r.common.Config(), + r.repo.Info.Name()), ".md") } c, cmd := r.code.Update(msg) r.code = c.(*code.Code) @@ -95,7 +100,11 @@ func (r *Readme) View() string { // StatusBarValue implements statusbar.StatusBar. func (r *Readme) StatusBarValue() string { - return "" + dir := filepath.Dir(r.readmePath) + if dir == "." { + return "" + } + return dir } // StatusBarInfo implements statusbar.StatusBar. @@ -109,6 +118,7 @@ func (r *Readme) updateReadmeCmd() tea.Msg { return common.ErrorCmd(git.ErrMissingRepo) } rm, rp := r.repo.Readme() + r.readmePath = rp r.code.GotoTop() cmd := r.code.SetContent(rm, rp) if cmd != nil { diff --git a/ui/pages/repo/refs.go b/ui/pages/repo/refs.go index 8dec6fc3a7e10db2d93393159c9673805f3ebf11..0868c578e2c86eb2cdfc6490e73bda7f01b58c0e 100644 --- a/ui/pages/repo/refs.go +++ b/ui/pages/repo/refs.go @@ -204,6 +204,9 @@ func UpdateRefCmd(repo *git.Repository) tea.Cmd { return func() tea.Msg { ref, err := repo.Repo.Repository().HEAD() if err != nil { + if bs, err := repo.Repo.Repository().Branches(); err != nil && len(bs) == 0 { + return EmptyRepoMsg{} + } log.Printf("ui: error getting HEAD reference: %v", err) return common.ErrorMsg(err) } diff --git a/ui/pages/repo/repo.go b/ui/pages/repo/repo.go index e57895097f84163a6f142d00f388eb9d9acba6fb..37b21fa136cd4050fc5f731de1d027859070537c 100644 --- a/ui/pages/repo/repo.go +++ b/ui/pages/repo/repo.go @@ -21,7 +21,7 @@ type state int const ( loadingState state = iota - loadedState + readyState ) type tab int @@ -45,6 +45,9 @@ func (t tab) String() string { }[t] } +// EmptyRepoMsg is a message to indicate that the repository is empty. +type EmptyRepoMsg struct{} + // CopyURLMsg is a message to copy the URL of the current repository. type CopyURLMsg struct{} @@ -257,6 +260,11 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, r.updateStatusBarCmd) case tea.WindowSizeMsg: cmds = append(cmds, r.updateModels(msg)) + case EmptyRepoMsg: + r.state = readyState + cmds = append(cmds, r.updateStatusBarCmd) + case common.ErrorMsg: + r.state = readyState } s, cmd := r.statusbar.Update(msg) r.statusbar = s.(*statusbar.StatusBar) @@ -290,7 +298,7 @@ func (r *Repo) View() string { switch r.state { case loadingState: main = fmt.Sprintf("%s loading…", r.spinner.View()) - case loadedState: + case readyState: main = r.panes[r.activeTab].View() statusbar = r.statusbar.View() } @@ -353,15 +361,15 @@ func (r *Repo) updateStatusBarCmd() tea.Msg { } value := r.panes[r.activeTab].(statusbar.Model).StatusBarValue() info := r.panes[r.activeTab].(statusbar.Model).StatusBarInfo() - ref := "" + branch := "*" if r.ref != nil { - ref = r.ref.Name().Short() + branch += " " + r.ref.Name().Short() } return statusbar.StatusBarMsg{ Key: r.selectedRepo.Info.Name(), Value: value, Info: info, - Branch: fmt.Sprintf("* %s", ref), + Branch: branch, } } @@ -418,7 +426,7 @@ func (r *Repo) updateRepo(msg tea.Msg) tea.Cmd { r.panesReady[readmeTab] = true } if r.isReady() { - r.state = loadedState + r.state = readyState } return tea.Batch(cmds...) } diff --git a/ui/styles/styles.go b/ui/styles/styles.go index 17bf175c0e28af2c93c44bae0f6b9ed84a67dca4..6f41e72b0143c21c1a73913570cfe162ad21f870 100644 --- a/ui/styles/styles.go +++ b/ui/styles/styles.go @@ -119,7 +119,6 @@ type Styles struct { Selector lipgloss.Style FileContent lipgloss.Style Paginator lipgloss.Style - NoItems lipgloss.Style } Spinner lipgloss.Style @@ -405,8 +404,6 @@ func DefaultStyles() *Styles { s.Tree.Paginator = s.Log.Paginator.Copy() - s.Tree.NoItems = s.AboutNoReadme.Copy() - s.Spinner = lipgloss.NewStyle(). MarginTop(1). MarginLeft(2). @@ -420,9 +417,7 @@ func DefaultStyles() *Styles { MarginLeft(2). Foreground(lipgloss.Color("242")) - s.NoItems = lipgloss.NewStyle(). - MarginLeft(2). - Foreground(lipgloss.Color("242")) + s.NoItems = s.AboutNoReadme.Copy() s.StatusBar = lipgloss.NewStyle(). Height(1) diff --git a/ui/ui.go b/ui/ui.go index 0e529f255d6d00001699bda8a8181a2c71df7797..8f747bbac57d9bef4a87ac66c7967ba808fc330e 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -27,9 +27,9 @@ const ( type sessionState int const ( - startState sessionState = iota + loadingState sessionState = iota errorState - loadedState + readyState ) // UI is the main UI model. @@ -58,7 +58,7 @@ func New(c common.Common, initialRepo string) *UI { common: c, pages: make([]common.Component, 2), // selection & repo activePage: selectionPage, - state: startState, + state: loadingState, header: h, initialRepo: initialRepo, showFooter: true, @@ -92,7 +92,7 @@ func (ui *UI) ShortHelp() []key.Binding { switch ui.state { case errorState: b = append(b, ui.common.KeyMap.Back) - case loadedState: + case readyState: b = append(b, ui.pages[ui.activePage].ShortHelp()...) } if !ui.IsFiltering() { @@ -108,7 +108,7 @@ func (ui *UI) FullHelp() [][]key.Binding { switch ui.state { case errorState: b = append(b, []key.Binding{ui.common.KeyMap.Back}) - case loadedState: + case readyState: b = append(b, ui.pages[ui.activePage].FullHelp()...) } h := []key.Binding{ @@ -147,7 +147,7 @@ func (ui *UI) Init() tea.Cmd { if ui.initialRepo != "" { cmds = append(cmds, ui.initialRepoCmd(ui.initialRepo)) } - ui.state = loadedState + ui.state = readyState ui.SetSize(ui.common.Width, ui.common.Height) return tea.Batch(cmds...) } @@ -182,7 +182,7 @@ func (ui *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch { case key.Matches(msg, ui.common.KeyMap.Back) && ui.error != nil: ui.error = nil - ui.state = loadedState + ui.state = readyState // Always show the footer on error. ui.showFooter = ui.footer.ShowAll() case key.Matches(msg, ui.common.KeyMap.Help): @@ -223,7 +223,6 @@ func (ui *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) { ui.error = msg ui.state = errorState ui.showFooter = true - return ui, nil case selector.SelectMsg: switch msg.IdentifiableItem.(type) { case selection.Item: @@ -242,7 +241,7 @@ func (ui *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if cmd != nil { cmds = append(cmds, cmd) } - if ui.state == loadedState { + if ui.state != loadingState { m, cmd := ui.pages[ui.activePage].Update(msg) ui.pages[ui.activePage] = m.(common.Component) if cmd != nil { @@ -259,7 +258,7 @@ func (ui *UI) View() string { var view string wm, hm := ui.getMargins() switch ui.state { - case startState: + case loadingState: view = "Loading..." case errorState: err := ui.common.Styles.ErrorTitle.Render("Bummer") @@ -272,7 +271,7 @@ func (ui *UI) View() string { hm - ui.common.Styles.Error.GetVerticalFrameSize()). Render(err) - case loadedState: + case readyState: view = ui.pages[ui.activePage].View() default: view = "Unknown state :/ this is a bug!"