| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- package main
- import (
- "fmt"
- "os/exec"
- "sort"
- "github.com/atotto/clipboard"
- "github.com/charmbracelet/bubbles/list"
- "github.com/charmbracelet/bubbles/textinput"
- tea "github.com/charmbracelet/bubbletea"
- )
- // handleWindowSize updates the model dimensions and adjusts the list size accordingly.
- func (m model) handleWindowSize(msg tea.WindowSizeMsg) (model, tea.Cmd) {
- m.width = msg.Width
- m.height = msg.Height
- if !m.loading && !m.promptingEmail {
- m.list.SetWidth(msg.Width)
- m.list.SetHeight(msg.Height - listHeightOffset)
- }
- return m, nil
- }
- // handleAuthRequired switches the model to email prompt mode.
- func (m model) handleAuthRequired() (model, tea.Cmd) {
- m.promptingEmail = true
- m.loading = false
- m.emailInput, m.rememberEmail = loadEmailIntoInput(m.emailInput)
- return m, textinput.Blink
- }
- // handleLogin processes the login result and saves the email if requested.
- func (m model) handleLogin(msg loginMsg) (model, tea.Cmd) {
- m.loggingIn = false
- if msg.err != nil {
- m.err = fmt.Sprintf("Login failed: %v", msg.err)
- return m, nil
- }
- if m.rememberEmail {
- email := m.emailInput.Value()
- if email != "" {
- saveConfig(email)
- }
- }
- m.promptingEmail = false
- m.loading = true
- return m, fetchStatus
- }
- // handleStatus processes the status message and updates the list with connections.
- // Connections are sorted with connected items first, then by name.
- func (m model) handleStatus(msg statusMsg) (model, tea.Cmd) {
- if msg.err != nil {
- m.loading = false
- m.err = fmt.Sprintf("Failed to fetch status: %v", msg.err)
- return m, nil
- }
- // Sort connections: connected items first, then by name
- connections := make([]connection, len(msg.connections))
- copy(connections, msg.connections)
- sort.Slice(connections, func(i, j int) bool {
- if connections[i].Connected != connections[j].Connected {
- // Connected items come first
- return connections[i].Connected
- }
- // If both have same connection status, sort by name
- return connections[i].Name < connections[j].Name
- })
- items := make([]list.Item, len(connections))
- for i, conn := range connections {
- items[i] = item{
- address: getAddress(conn),
- name: conn.Name,
- connected: conn.Connected,
- connType: conn.Type,
- }
- }
- m.list = newList(items, m.width, m.height-listHeightOffset)
- m.loading = false
- return m, nil
- }
- // handleConnection initiates a connection to the selected item.
- func (m model) handleConnection() (model, tea.Cmd) {
- i, ok := m.list.SelectedItem().(item)
- if !ok {
- return m, nil
- }
- m.loading = true
- name := i.name
- address := i.address
- return m, tea.Batch(
- func() tea.Msg {
- return connectSDM(name, address)
- },
- m.spinner.Tick,
- )
- }
- // handleConnectResult processes the result of a connect operation.
- func (m model) handleConnectResult(msg connectMsg) (model, tea.Cmd) {
- m.loading = false
- if msg.err != nil {
- m.err = fmt.Sprintf("Error connecting to %s: %v", msg.name, msg.err)
- return m, nil
- }
- clipboard.WriteAll(msg.address)
- sendNotification("SDM Connected", fmt.Sprintf("Connected to %s", msg.name))
- return m, tea.Quit
- }
- // handleKillConnections disconnects all connections.
- func (m model) handleKillConnections() (model, tea.Cmd) {
- m.loading = true
- return m, tea.Batch(
- func() tea.Msg {
- cmd := exec.Command("sdm", "disconnect", "--all")
- output, err := cmd.CombinedOutput()
- if err != nil {
- return disconnectMsg{err: fmt.Errorf("disconnecting: %v: %s", err, string(output))}
- }
- return disconnectMsg{}
- },
- m.spinner.Tick,
- )
- }
- // handleDisconnectSelected disconnects the currently selected connection.
- func (m model) handleDisconnectSelected() (model, tea.Cmd) {
- i, ok := m.list.SelectedItem().(item)
- if !ok || !i.connected {
- return m, nil
- }
- m.loading = true
- name := i.name
- return m, tea.Batch(
- func() tea.Msg {
- return disconnectSDM(name)
- },
- m.spinner.Tick,
- )
- }
- // handleDisconnect processes the result of a disconnect operation.
- func (m model) handleDisconnect(msg disconnectMsg) (model, tea.Cmd) {
- m.loading = false
- if msg.err != nil {
- m.err = fmt.Sprintf("Error disconnecting: %v", msg.err)
- return m, nil
- }
- name := "all connections"
- if msg.name != "" {
- name = msg.name
- }
- sendNotification("SDM Disconnected", fmt.Sprintf("Disconnected from %s", name))
- m.loading = true
- return m, tea.Batch(fetchStatus, m.spinner.Tick)
- }
- // handleKeyInput processes keyboard input based on the current application state.
- func (m model) handleKeyInput(msg tea.KeyMsg) (model, tea.Cmd) {
- if m.promptingEmail {
- switch msg.String() {
- case "enter":
- email := m.emailInput.Value()
- if email != "" && !m.loggingIn {
- m.loggingIn = true
- return m, tea.Batch(
- func() tea.Msg {
- return loginSDM(email)
- },
- m.spinner.Tick,
- )
- }
- case " ":
- if !m.loggingIn {
- m.rememberEmail = !m.rememberEmail
- }
- return m, nil
- }
- if isQuitKey(msg.String()) {
- return m, tea.Quit
- }
- if !m.loggingIn {
- var cmd tea.Cmd
- m.emailInput, cmd = m.emailInput.Update(msg)
- return m, cmd
- }
- return m, nil
- }
- if m.err != "" {
- switch msg.String() {
- case "r":
- m.err = ""
- m.loading = true
- m.promptingEmail = false
- m.loggingIn = false
- return m, tea.Batch(
- func() tea.Msg {
- if err := checkSDMBinary(); err != nil {
- return statusMsg{err: err}
- }
- return fetchStatus()
- },
- m.spinner.Tick,
- )
- }
- if isQuitKey(msg.String()) {
- return m, tea.Quit
- }
- return m, nil
- }
- if m.loading {
- return m, nil
- }
- if m.list.FilterState() == list.Filtering {
- var cmd tea.Cmd
- m.list, cmd = m.list.Update(msg)
- return m, cmd
- }
- switch msg.String() {
- case tea.KeyEnter.String():
- return m.handleConnection()
- case tea.KeyCtrlX.String():
- return m.handleKillConnections()
- case "d":
- return m.handleDisconnectSelected()
- case "r":
- m.loading = true
- return m, tea.Batch(fetchStatus, m.spinner.Tick)
- }
- if isQuitKey(msg.String()) {
- return m, tea.Quit
- }
- var cmd tea.Cmd
- m.list, cmd = m.list.Update(msg)
- return m, cmd
- }
|