Allow moving tickets between projects with P.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+84
-2
@@ -39,6 +39,7 @@ const (
|
||||
ModeCreate
|
||||
ModeSearch
|
||||
ModeProjects
|
||||
ModeMoveTicket
|
||||
ModeStatus
|
||||
ModeBoard
|
||||
ModeConfirmDelete
|
||||
@@ -166,6 +167,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m.updateInput(msg)
|
||||
case ModeProjects:
|
||||
return m.updateProjects(msg)
|
||||
case ModeMoveTicket:
|
||||
return m.updateMoveTicket(msg)
|
||||
case ModeStatus:
|
||||
return m.updateStatus(msg)
|
||||
case ModeBoard:
|
||||
@@ -185,7 +188,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
var cmd tea.Cmd
|
||||
switch m.mode {
|
||||
case ModeProjects:
|
||||
case ModeProjects, ModeMoveTicket:
|
||||
m.projectList, cmd = m.projectList.Update(msg)
|
||||
case ModeCreate, ModeSearch, ModeTimeManual, ModeTimeEdit:
|
||||
m.input, cmd = m.input.Update(msg)
|
||||
@@ -216,7 +219,7 @@ func (m *Model) resizeLists() {
|
||||
bodyH = 1
|
||||
}
|
||||
m.tickets.SetSize(m.width, bodyH)
|
||||
if m.mode == ModeProjects {
|
||||
if m.mode == ModeProjects || m.mode == ModeMoveTicket {
|
||||
m.projectList.SetSize(m.width, bodyH)
|
||||
}
|
||||
m.input.Width = max(10, m.width-4)
|
||||
@@ -258,6 +261,19 @@ func (m Model) updateList(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
m.mode = ModeProjects
|
||||
return m, nil
|
||||
|
||||
case key.Matches(msg, m.keys.MoveTicket):
|
||||
m.clearStatus()
|
||||
if _, ok := m.tickets.Selected(); !ok {
|
||||
m.setError(fmt.Errorf("no ticket selected"))
|
||||
return m, nil
|
||||
}
|
||||
if err := m.openProjects(); err != nil {
|
||||
m.setError(err)
|
||||
return m, nil
|
||||
}
|
||||
m.mode = ModeMoveTicket
|
||||
return m, nil
|
||||
|
||||
case key.Matches(msg, m.keys.Board):
|
||||
m.clearStatus()
|
||||
m.boardMenu = views.NewBoardMenu(m.boardFilter, m.width, views.StatusMenuStyles{
|
||||
@@ -716,6 +732,37 @@ func (m Model) updateReportGroup(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m Model) updateMoveTicket(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch {
|
||||
case key.Matches(msg, m.keys.Escape):
|
||||
m.mode = ModeList
|
||||
m.clearStatus()
|
||||
return m, nil
|
||||
|
||||
case key.Matches(msg, m.keys.Enter):
|
||||
item, ok := m.projectList.SelectedItem().(views.ProjectItem)
|
||||
if !ok {
|
||||
return m, nil
|
||||
}
|
||||
if err := m.moveSelectedToProject(item.Project.ID); err != nil {
|
||||
m.setError(err)
|
||||
return m, nil
|
||||
}
|
||||
m.mode = ModeList
|
||||
return m, nil
|
||||
}
|
||||
|
||||
if msg.Type == tea.KeyRunes && len(msg.Runes) == 1 {
|
||||
if views.JumpProjectByLetter(&m.projectList, msg.Runes[0]) {
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
m.projectList, cmd = m.projectList.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m Model) updateProjects(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch {
|
||||
case key.Matches(msg, m.keys.Escape):
|
||||
@@ -994,6 +1041,41 @@ func (m *Model) switchProject(id string) error {
|
||||
return m.reloadTickets()
|
||||
}
|
||||
|
||||
func (m *Model) moveSelectedToProject(toProjectID string) error {
|
||||
t, ok := m.tickets.Selected()
|
||||
if !ok {
|
||||
return fmt.Errorf("no ticket selected")
|
||||
}
|
||||
if toProjectID == t.ProjectID {
|
||||
m.setStatus("already there")
|
||||
return nil
|
||||
}
|
||||
if err := m.projects.Ensure(toProjectID); err != nil {
|
||||
return err
|
||||
}
|
||||
after := t
|
||||
after.ProjectID = toProjectID
|
||||
if err := m.exec.Do(&command.MoveTicket{
|
||||
Store: m.store,
|
||||
TimeStore: m.timeStore,
|
||||
Before: t,
|
||||
After: after,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if m.activeTimer != nil && m.activeTimer.TicketID == t.ID && m.timeStore != nil {
|
||||
m.activeTimer.ProjectID = toProjectID
|
||||
if err := m.timeStore.SaveActiveTimer(m.activeTimer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := m.reloadTickets(); err != nil {
|
||||
return err
|
||||
}
|
||||
m.setStatus(fmt.Sprintf("moved → %s", toProjectID))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) countTickets(projectID string) (int, error) {
|
||||
tickets, err := m.store.List(projectID)
|
||||
if err != nil {
|
||||
|
||||
@@ -37,6 +37,13 @@ func (m Model) helpEntries() []helpEntry {
|
||||
{"X", "force delete"},
|
||||
{"esc", "cancel"},
|
||||
}
|
||||
case ModeMoveTicket:
|
||||
return []helpEntry{
|
||||
{"j/k", "navigate"},
|
||||
{"a-z", "jump"},
|
||||
{"enter", "move here"},
|
||||
{"esc", "cancel"},
|
||||
}
|
||||
case ModeStatus:
|
||||
return []helpEntry{
|
||||
{"j/k", "select"},
|
||||
@@ -99,6 +106,7 @@ func (m Model) helpEntries() []helpEntry {
|
||||
{"u/U", "undo/redo"},
|
||||
{"/", "search"},
|
||||
{"p", "project"},
|
||||
{"P", "move"},
|
||||
{"l", "list"},
|
||||
{"s", "status"},
|
||||
{"x", "delete"},
|
||||
|
||||
+6
-1
@@ -9,6 +9,7 @@ type KeyMap struct {
|
||||
New key.Binding
|
||||
Search key.Binding
|
||||
Project key.Binding
|
||||
MoveTicket key.Binding // P — move selected ticket to another project
|
||||
Board key.Binding
|
||||
Status key.Binding
|
||||
Edit key.Binding
|
||||
@@ -51,6 +52,10 @@ func DefaultKeyMap() KeyMap {
|
||||
key.WithKeys("p"),
|
||||
key.WithHelp("p", "project"),
|
||||
),
|
||||
MoveTicket: key.NewBinding(
|
||||
key.WithKeys("P"),
|
||||
key.WithHelp("P", "move ticket"),
|
||||
),
|
||||
Board: key.NewBinding(
|
||||
key.WithKeys("l", "tab"),
|
||||
key.WithHelp("l", "list"),
|
||||
@@ -124,7 +129,7 @@ func DefaultKeyMap() KeyMap {
|
||||
|
||||
// ShortHelp implements help.KeyMap.
|
||||
func (k KeyMap) ShortHelp() []key.Binding {
|
||||
return []key.Binding{k.Up, k.New, k.Search, k.Project, k.Board, k.Status, k.Edit, k.Timer, k.TimeManual, k.TimeEntries, k.Report, k.Delete, k.DeleteForce, k.Undo, k.Redo, k.Quit}
|
||||
return []key.Binding{k.Up, k.New, k.Search, k.Project, k.MoveTicket, k.Board, k.Status, k.Edit, k.Timer, k.TimeManual, k.TimeEntries, k.Report, k.Delete, k.DeleteForce, k.Undo, k.Redo, k.Quit}
|
||||
}
|
||||
|
||||
// FullHelp implements help.KeyMap.
|
||||
|
||||
@@ -76,7 +76,7 @@ func (m Model) renderBody(height int) string {
|
||||
menu := m.groupByMenu.View()
|
||||
return lipgloss.NewStyle().Height(height).Width(m.width).Render(menu)
|
||||
|
||||
case ModeProjects:
|
||||
case ModeProjects, ModeMoveTicket:
|
||||
m.projectList.SetSize(m.width, height)
|
||||
return m.projectList.View()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user