diff --git a/internal/quickstart/choose_sdk.go b/internal/quickstart/choose_sdk.go new file mode 100644 index 00000000..025f7a60 --- /dev/null +++ b/internal/quickstart/choose_sdk.go @@ -0,0 +1,134 @@ +package quickstart + +import ( + "fmt" + "io" + "strings" + + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +var ( + sdkStyle = lipgloss.NewStyle().PaddingLeft(4) + selectedSdkItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170")) +) + +type chooseSDKModel struct { + list list.Model + selectedSdk sdkDetail +} + +func NewChooseSDKModel() tea.Model { + l := list.New(sdksToItems(), sdkDelegate{}, 30, 14) + l.Title = "Select your SDK:\n" + // reset title styles + l.Styles.Title = lipgloss.NewStyle() + l.Styles.TitleBar = lipgloss.NewStyle() + l.SetShowPagination(true) + l.SetShowStatusBar(false) + l.SetFilteringEnabled(false) // TODO: try to get filtering working + l.Paginator.PerPage = 5 + + return chooseSDKModel{ + list: l, + } +} + +func (m chooseSDKModel) Init() tea.Cmd { return nil } + +func (m chooseSDKModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, keys.Enter): + i, ok := m.list.SelectedItem().(sdkDetail) + if ok { + m.selectedSdk = i + } + case key.Matches(msg, keys.Quit): + return m, tea.Quit + default: + m.list, cmd = m.list.Update(msg) + } + } + + return m, cmd +} + +func (m chooseSDKModel) View() string { + return m.list.View() +} + +type sdkDetail struct { + DisplayName string `json:"displayName"` + SDKType string `json:"sdkType"` +} + +func (s sdkDetail) FilterValue() string { return "" } + +const clientSideSDK = "client" +const serverSideSDK = "server" + +var SDKs = []sdkDetail{ + {DisplayName: "React", SDKType: clientSideSDK}, + {DisplayName: "Node.js (server-side)", SDKType: serverSideSDK}, + {DisplayName: "Python", SDKType: serverSideSDK}, + {DisplayName: "Java", SDKType: serverSideSDK}, + {DisplayName: ".NET (server-side)", SDKType: serverSideSDK}, + {DisplayName: "JavaScript", SDKType: clientSideSDK}, + {DisplayName: "Vue", SDKType: clientSideSDK}, + {DisplayName: "iOS", SDKType: clientSideSDK}, + {DisplayName: "Go", SDKType: serverSideSDK}, + {DisplayName: "Android", SDKType: clientSideSDK}, + {DisplayName: "React Native", SDKType: clientSideSDK}, + {DisplayName: "Ruby", SDKType: serverSideSDK}, + {DisplayName: "Flutter", SDKType: clientSideSDK}, + {DisplayName: ".NET (client-side)", SDKType: clientSideSDK}, + {DisplayName: "Erlang", SDKType: serverSideSDK}, + {DisplayName: "Rust", SDKType: serverSideSDK}, + {DisplayName: "Electron", SDKType: clientSideSDK}, + {DisplayName: "C/C++ (client-side)", SDKType: clientSideSDK}, + {DisplayName: "Roku", SDKType: clientSideSDK}, + {DisplayName: "Node.js (client-side)", SDKType: clientSideSDK}, + {DisplayName: "C/C++ (server-side)", SDKType: serverSideSDK}, + {DisplayName: "Lua", SDKType: serverSideSDK}, + {DisplayName: "Haskell", SDKType: serverSideSDK}, + {DisplayName: "Apex", SDKType: serverSideSDK}, + {DisplayName: "PHP", SDKType: serverSideSDK}, +} + +func sdksToItems() []list.Item { + items := make([]list.Item, len(SDKs)) + for i, sdk := range SDKs { + items[i] = list.Item(sdk) + } + + return items +} + +type sdkDelegate struct{} + +func (d sdkDelegate) Height() int { return 1 } +func (d sdkDelegate) Spacing() int { return 0 } +func (d sdkDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil } +func (d sdkDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { + i, ok := listItem.(sdkDetail) + if !ok { + return + } + + str := fmt.Sprintf("%d. %s", index+1, i.DisplayName) + + fn := sdkStyle.Render + if index == m.Index() { + fn = func(s ...string) string { + return selectedSdkItemStyle.Render("> " + strings.Join(s, " ")) + } + } + + fmt.Fprint(w, fn(str)) +} diff --git a/internal/quickstart/container.go b/internal/quickstart/container.go index 865dfb32..79afa761 100644 --- a/internal/quickstart/container.go +++ b/internal/quickstart/container.go @@ -15,6 +15,7 @@ type step int const ( createFlagStep step = iota + chooseSDKStep ) // ContainerModel is a high level container model that controls the nested models wher each @@ -25,6 +26,7 @@ type ContainerModel struct { flagKey string flagsClient flags.Client quitting bool + sdk sdkDetail steps []tea.Model } @@ -34,6 +36,7 @@ func NewContainerModel(flagsClient flags.Client) tea.Model { flagsClient: flagsClient, steps: []tea.Model{ NewCreateFlagModel(flagsClient), + NewChooseSDKModel(), }, } } @@ -53,12 +56,18 @@ func (m ContainerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if model, ok := updated.(createFlagModel); ok { if model.err != nil { m.err = model.err - return m, nil } m.flagKey = model.flagKey m.currentStep += 1 } + case chooseSDKStep: + updated, _ := m.steps[chooseSDKStep].Update(msg) + if model, ok := updated.(chooseSDKModel); ok { + // no error state for this step + m.sdk = model.selectedSdk + m.currentStep += 1 + } default: } case key.Matches(msg, keys.Quit): @@ -90,8 +99,8 @@ func (m ContainerModel) View() string { } // TODO: remove after creating more steps - if m.currentStep > createFlagStep { - return fmt.Sprintf("created flag %s", m.flagKey) + if m.currentStep > chooseSDKStep { + return fmt.Sprintf("created flag %s\nselected the %s SDK", m.flagKey, m.sdk.DisplayName) } return fmt.Sprintf("\nStep %d of %d\n"+m.steps[m.currentStep].View(), m.currentStep+1, len(m.steps))