Introduces the web backend (auth, RBAC, project tokens, revisions), YouTrack-or-taskmaster backend choice in the TUI, and a first-pass embedded Svelte UI. The frontend is functional but needs a major rework. Co-authored-by: Cursor <cursoragent@cursor.com>
98 lines
2.1 KiB
Go
98 lines
2.1 KiB
Go
package views
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
// SyncAction is a sync menu choice.
|
|
type SyncAction string
|
|
|
|
const (
|
|
SyncActionPull SyncAction = "pull"
|
|
SyncActionPush SyncAction = "push"
|
|
)
|
|
|
|
// SyncOption is one row in the sync menu.
|
|
type SyncOption struct {
|
|
Action SyncAction
|
|
Label string
|
|
Shortcut rune
|
|
}
|
|
|
|
// SyncMenu renders a pull/push picker overlay.
|
|
type SyncMenu struct {
|
|
Cursor int
|
|
Width int
|
|
Title string
|
|
Options []SyncOption
|
|
Styles StatusMenuStyles
|
|
}
|
|
|
|
// NewSyncMenu creates a sync action menu for the given backend label.
|
|
func NewSyncMenu(width int, styles StatusMenuStyles, backendLabel string) SyncMenu {
|
|
if backendLabel == "" {
|
|
backendLabel = "Sync"
|
|
}
|
|
opts := []SyncOption{
|
|
{Action: SyncActionPull, Label: "Pull from " + backendLabel, Shortcut: 'p'},
|
|
{Action: SyncActionPush, Label: "Push to " + backendLabel, Shortcut: 'u'},
|
|
}
|
|
return SyncMenu{Cursor: 0, Width: width, Title: backendLabel, Options: opts, Styles: styles}
|
|
}
|
|
|
|
// MoveDown moves the selection down.
|
|
func (m *SyncMenu) MoveDown() {
|
|
if m.Cursor < len(m.Options)-1 {
|
|
m.Cursor++
|
|
}
|
|
}
|
|
|
|
// MoveUp moves the selection up.
|
|
func (m *SyncMenu) MoveUp() {
|
|
if m.Cursor > 0 {
|
|
m.Cursor--
|
|
}
|
|
}
|
|
|
|
// JumpToShortcut selects the option whose shortcut matches r.
|
|
func (m *SyncMenu) JumpToShortcut(r rune) bool {
|
|
r = unicode.ToLower(r)
|
|
for i, opt := range m.Options {
|
|
if opt.Shortcut == r {
|
|
m.Cursor = i
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Selected returns the highlighted action.
|
|
func (m SyncMenu) Selected() SyncAction {
|
|
if m.Cursor < 0 || m.Cursor >= len(m.Options) {
|
|
return SyncActionPull
|
|
}
|
|
return m.Options[m.Cursor].Action
|
|
}
|
|
|
|
// View renders the sync menu.
|
|
func (m SyncMenu) View() string {
|
|
var lines []string
|
|
lines = append(lines, m.Styles.Title.Render(m.Title))
|
|
for i, opt := range m.Options {
|
|
entry := fmt.Sprintf("%c %s", opt.Shortcut, opt.Label)
|
|
if i == m.Cursor {
|
|
lines = append(lines, m.Styles.Selected.Render("▸ "+entry))
|
|
} else {
|
|
lines = append(lines, m.Styles.Normal.Render(" "+entry))
|
|
}
|
|
}
|
|
body := strings.Join(lines, "\n")
|
|
w := m.Width
|
|
if w < 20 {
|
|
w = 20
|
|
}
|
|
return m.Styles.Box.Width(min(44, w-2)).Render(body)
|
|
}
|