diff --git a/api/api.go b/api/api.go index e0a0aa4..4adbcf2 100644 --- a/api/api.go +++ b/api/api.go @@ -207,7 +207,7 @@ func (m *Api) GetTasksFromList(listId string) ([]clickup.Task, error) { return m.getTasksFromList(true, listId) } -func (m *Api) syncTasksFromList(listId string) ([]clickup.Task, error) { +func (m *Api) SyncTasksFromList(listId string) ([]clickup.Task, error) { return m.getTasksFromList(false, listId) } @@ -230,7 +230,7 @@ func (m *Api) GetTasksFromView(viewId string) ([]clickup.Task, error) { return m.getTasksFromView(true, viewId) } -func (m *Api) syncTasksFromView(viewId string) ([]clickup.Task, error) { +func (m *Api) SyncTasksFromView(viewId string) ([]clickup.Task, error) { return m.getTasksFromView(false, viewId) } @@ -430,9 +430,9 @@ func (m *Api) Sync() error { case CacheNamespaceViewsList: _, err = m.syncViewsFromList(key) case CacheNamespaceTasksList: - _, err = m.syncTasksFromList(key) + _, err = m.SyncTasksFromList(key) case CacheNamespaceTasksView: - _, err = m.syncTasksFromView(key) + _, err = m.SyncTasksFromView(key) case CacheNamespaceTasks: _, err = m.SyncTask(key) default: diff --git a/pkg/clickup/task.go b/pkg/clickup/task.go index eb6a5cc..f215c8d 100644 --- a/pkg/clickup/task.go +++ b/pkg/clickup/task.go @@ -78,11 +78,14 @@ type TaskSpace struct { } func (t Task) GetAssignees() string { - assignees := []string{} - for _, assignee := range t.Assignees { - assignees = append(assignees, assignee.Username) + assignees := strings.Builder{} + for i := range t.Assignees { + assignees.WriteString(t.Assignees[i].Username) + if i != len(t.Assignees) { + assignees.WriteString(", ") + } } - return strings.Join(assignees, ",") + return assignees.String() } type Assignee struct { diff --git a/ui/common/utils.go b/ui/common/utils.go index 66adb4d..8abd3c8 100644 --- a/ui/common/utils.go +++ b/ui/common/utils.go @@ -23,11 +23,12 @@ func OpenUrlInWebBrowser(url string) error { } type EditorFinishedMsg struct { + Id string Data interface{} Err error } -func OpenEditor(data string) tea.Cmd { +func OpenEditor(id string, data string) tea.Cmd { editor := os.Getenv("EDITOR") if editor == "" { editor = "vim" @@ -49,10 +50,10 @@ func OpenEditor(data string) tea.Cmd { } updatedData, err := os.ReadFile(tmpfileName) - data = string(updatedData) return EditorFinishedMsg{ - Data: data, + Id: id, + Data: string(updatedData), Err: err, } }) diff --git a/ui/components/table-tasks/taskstable.go b/ui/components/table-tasks/taskstable.go index 9e33407..b3959d9 100644 --- a/ui/components/table-tasks/taskstable.go +++ b/ui/components/table-tasks/taskstable.go @@ -80,6 +80,7 @@ func (m *Model) setTableSize(s common.Size) { type KeyMap struct { table.KeyMap + Select key.Binding } func (m Model) KeyMap() KeyMap { @@ -104,6 +105,10 @@ func DefaultKeyMap() KeyMap { ScrollRight: common.KeyBindingWithHelp(km.ScrollRight, "scroll right"), ScrollLeft: common.KeyBindingWithHelp(km.ScrollLeft, "scroll left"), }, + Select: key.NewBinding( + key.WithKeys("enter"), + key.WithHelp("enter", "select"), + ), } } @@ -132,6 +137,7 @@ func (m Model) Help() help.KeyMap { { km.ScrollRight, km.ScrollLeft, + m.keyMap.Select, }, } }, @@ -142,6 +148,7 @@ func (m Model) Help() help.KeyMap { km.RowSelectToggle, km.PageDown, km.PageUp, + m.keyMap.Select, } }, ) @@ -245,8 +252,8 @@ func (m *Model) Update(msg tea.Msg) tea.Cmd { switch msg := msg.(type) { case tea.KeyMsg: - switch keypress := msg.String(); keypress { - case "enter": + switch { + case key.Matches(msg, m.keyMap.Select): index := m.table.GetHighlightedRowIndex() if m.table.TotalRows() == 0 { m.log.Info("Table is empty") diff --git a/ui/components/tasks-sidebar/tasksidebar.go b/ui/components/tasks-sidebar/tasksidebar.go index 5f95be0..74850ed 100644 --- a/ui/components/tasks-sidebar/tasksidebar.go +++ b/ui/components/tasks-sidebar/tasksidebar.go @@ -39,7 +39,6 @@ func (m Model) Id() common.Id { type KeyMap struct { viewport.KeyMap - Edit key.Binding } func (m Model) KeyMap() KeyMap { @@ -49,10 +48,6 @@ func (m Model) KeyMap() KeyMap { func DefaultKeyMap() KeyMap { return KeyMap{ KeyMap: viewport.DefaultKeyMap(), - Edit: key.NewBinding( - key.WithKeys("e"), - key.WithHelp("e", "edit task"), - ), } } @@ -85,9 +80,6 @@ func (m Model) Help() help.KeyMap { km.HalfPageUp, km.HalfPageDown, }, - { - km.Edit, - }, } }, func() []key.Binding { @@ -96,7 +88,6 @@ func (m Model) Help() help.KeyMap { km.Up, km.PageDown, km.PageUp, - km.Edit, } }, ) @@ -136,35 +127,6 @@ func (m *Model) Update(msg tea.Msg) tea.Cmd { cmds []tea.Cmd ) - switch msg := msg.(type) { - case tea.KeyMsg: - switch { - case key.Matches(msg, m.keyMap.Edit): - data := m.SelectedTask.MarkdownDescription - cmds = append(cmds, common.OpenEditor(data)) - } - - case common.EditorFinishedMsg: - data := msg.Data.(string) - - m.SelectedTask.Description = data - cmds = append(cmds, UpdateTaskCmd(m.SelectedTask)) - - if err := m.setTask(m.SelectedTask); err != nil { - return common.ErrCmd(err) - } - - case UpdateTaskMsg: - t, err := m.ctx.Api.UpdateTask(m.SelectedTask) - if err != nil { - return common.ErrCmd(err) - } - - if err := m.setTask(t); err != nil { - return common.ErrCmd(err) - } - } - m.viewport, cmd = m.viewport.Update(msg) cmds = append(cmds, cmd) @@ -246,8 +208,7 @@ func (m Model) WithHidden(h bool) Model { return m } -func (m *Model) SetTask(id string) error { - m.log.Infof("Received: TaskSelectedMsg: %s", id) +func (m *Model) SelectTask(id string) error { m.Ready = false task, err := m.ctx.Api.GetTask(id) @@ -255,7 +216,7 @@ func (m *Model) SetTask(id string) error { return err } - if err := m.setTask(task); err != nil { + if err := m.SetTask(task); err != nil { return err } m.Ready = true @@ -263,7 +224,7 @@ func (m *Model) SetTask(id string) error { return nil } -func (m *Model) setTask(task clickup.Task) error { +func (m *Model) SetTask(task clickup.Task) error { m.SelectedTask = task renderedTask, err := m.renderTask(task) if err != nil { @@ -275,11 +236,3 @@ func (m *Model) setTask(task clickup.Task) error { return nil } - -type UpdateTaskMsg clickup.Task - -func UpdateTaskCmd(task clickup.Task) tea.Cmd { - return func() tea.Msg { - return UpdateTaskMsg(task) - } -} diff --git a/ui/theme/theme.go b/ui/theme/theme.go index e5fcd7e..9659472 100644 --- a/ui/theme/theme.go +++ b/ui/theme/theme.go @@ -13,10 +13,12 @@ type Theme struct { BordersColorActive lipgloss.Color BordersColorInactive lipgloss.Color BordersColorCopyMode lipgloss.Color + BordersColorEditMode lipgloss.Color } var DefaultTheme = &Theme{ BordersColorActive: lipgloss.Color("#8909FF"), BordersColorInactive: lipgloss.Color("#FFF"), BordersColorCopyMode: lipgloss.Color("#e6cc00"), + BordersColorEditMode: lipgloss.Color("#e6cc00"), } diff --git a/ui/views/compact/compact.go b/ui/views/compact/compact.go index abd4def..483e39b 100644 --- a/ui/views/compact/compact.go +++ b/ui/views/compact/compact.go @@ -208,20 +208,20 @@ func (m *Model) Update(msg tea.Msg) tea.Cmd { case LoadingTasksFromViewMsg: id := string(msg) + m.log.Info("Received: LoadingTasksFromViewMsg", "id", id) m.widgetTasks.SetSpinner(false) if id == "" { - m.log.Info("Received: LoadingTasksFromViewMsg empty") m.widgetTasks.SetTasks(nil) break } - m.log.Info("Received: LoadingTasksFromViewMsg", "id", id) if err := m.reloadTasks(id); err != nil { cmds = append(cmds, common.ErrCmd(err)) - return tea.Batch(cmds...) } + return tea.Batch(cmds...) + case tasks.LostFocusMsg: m.log.Info("Received: tasks.LostFocusMsg") m.state = m.widgetNavigator.Id() @@ -327,6 +327,7 @@ func (m *Model) reloadTasks(viewId string) error { return err } m.widgetTasks.SetTasks(tasks) + m.widgetTasks.SelectedViewListId = viewId return nil } diff --git a/ui/widgets/tasks/commands.go b/ui/widgets/tasks/commands.go index b631c01..be87877 100644 --- a/ui/widgets/tasks/commands.go +++ b/ui/widgets/tasks/commands.go @@ -1,6 +1,9 @@ package tasks -import tea "github.com/charmbracelet/bubbletea" +import ( + tea "github.com/charmbracelet/bubbletea" + "github.com/prgrs/clickup/pkg/clickup" +) type LostFocusMsg string @@ -9,3 +12,11 @@ func LostFocusCmd() tea.Cmd { return LostFocusMsg("") } } + +type UpdateTaskMsg clickup.Task + +func UpdateTaskCmd(task clickup.Task) tea.Cmd { + return func() tea.Msg { + return UpdateTaskMsg(task) + } +} diff --git a/ui/widgets/tasks/tasks.go b/ui/widgets/tasks/tasks.go index a57ca0c..510982b 100644 --- a/ui/widgets/tasks/tasks.go +++ b/ui/widgets/tasks/tasks.go @@ -17,21 +17,29 @@ import ( "golang.design/x/clipboard" ) -const id = "tasks" +const ( + id = "tasks" + editorIdDescription = "description" + editorIdName = "name" + editorIdStatus = "status" +) type Model struct { - log *log.Logger - ctx *context.UserContext - id common.Id - size common.Size - Focused bool - Hidden bool - ifBorders bool - keyMap KeyMap - state common.Id - spinner spinner.Model - showSpinner bool - copyMode bool // TODO make as a widget + log *log.Logger + ctx *context.UserContext + id common.Id + size common.Size + Focused bool + Hidden bool + ifBorders bool + keyMap KeyMap + state common.Id + spinner spinner.Model + showSpinner bool + SelectedViewListId string + + copyMode bool // TODO make as a widget + editMode bool componenetTasksTable *tabletasks.Model componenetTasksSidebar *taskssidebar.Model @@ -69,6 +77,7 @@ func InitialModel(ctx *context.UserContext, logger *log.Logger) Model { spinner: s, showSpinner: false, copyMode: false, + editMode: false, componenetTasksTable: &componenetTasksTable, componenetTasksSidebar: &componenetTasksSidebar, } @@ -83,6 +92,12 @@ type KeyMap struct { CopyTaskUrl key.Binding CopyTaskUrlMd key.Binding LostFocus key.Binding + EditMode key.Binding + EditDescription key.Binding + EditName key.Binding + EditStatus key.Binding + EditAssigness key.Binding + EditQuit key.Binding Refresh key.Binding } @@ -124,6 +139,30 @@ func DefaultKeyMap() KeyMap { key.WithKeys("R"), key.WithHelp("R", "go to refresh"), ), + EditMode: key.NewBinding( + key.WithKeys("e"), + key.WithHelp("e", "edit mode"), + ), + EditDescription: key.NewBinding( + key.WithKeys("e"), + key.WithHelp("e", "edit description"), + ), + EditName: key.NewBinding( + key.WithKeys("n"), + key.WithHelp("n", "edit name"), + ), + EditStatus: key.NewBinding( + key.WithKeys("s"), + key.WithHelp("s", "edit status"), + ), + EditAssigness: key.NewBinding( + key.WithKeys("a"), + key.WithHelp("a", "edit assigness"), + ), + EditQuit: key.NewBinding( + key.WithKeys("esc"), + key.WithHelp("esc", "quit edit mode"), + ), } } @@ -155,6 +194,30 @@ func (m Model) Help() help.KeyMap { ) } + if m.editMode { + return common.NewHelp( + func() [][]key.Binding { + return [][]key.Binding{ + { + m.keyMap.EditDescription, + m.keyMap.EditName, + m.keyMap.EditStatus, + m.keyMap.EditAssigness, + m.keyMap.EditQuit, + }, + } + }, + func() []key.Binding { + return []key.Binding{ + m.keyMap.EditDescription, + m.keyMap.EditName, + m.keyMap.EditStatus, + m.keyMap.EditAssigness, + m.keyMap.EditQuit, + } + }, + ) + } switch m.state { case m.componenetTasksSidebar.Id(): help = m.componenetTasksSidebar.Help() @@ -169,6 +232,7 @@ func (m Model) Help() help.KeyMap { []key.Binding{ m.keyMap.OpenTicketInWebBrowser, m.keyMap.ToggleSidebar, + m.keyMap.EditMode, }, ) }, @@ -177,6 +241,7 @@ func (m Model) Help() help.KeyMap { help.ShortHelp(), m.keyMap.OpenTicketInWebBrowser, m.keyMap.CopyMode, + m.keyMap.EditMode, ) }, ) @@ -217,6 +282,34 @@ func (m *Model) Update(msg tea.Msg) tea.Cmd { return tea.Batch(cmds...) } + if m.editMode { + switch { + case key.Matches(msg, m.keyMap.EditDescription): + data := m.componenetTasksSidebar.SelectedTask.MarkdownDescription + cmds = append(cmds, common.OpenEditor(editorIdDescription, data)) + m.editMode = false + + case key.Matches(msg, m.keyMap.EditName): + data := m.componenetTasksSidebar.SelectedTask.Name + cmds = append(cmds, common.OpenEditor(editorIdName, data)) + m.editMode = false + + case key.Matches(msg, m.keyMap.EditStatus): + data := m.componenetTasksSidebar.SelectedTask.Status.Status + cmds = append(cmds, common.OpenEditor(editorIdStatus, data)) + m.editMode = false + + // case key.Matches(msg, m.keyMap.EditAssigness): + // data := m.SelectedTask.Assignees + // cmds = append(cmds, common.OpenEditor(data)) + // m.editMode = false + + case key.Matches(msg, m.keyMap.EditQuit): + m.editMode = false + } + + return tea.Batch(cmds...) + } switch { case key.Matches(msg, m.keyMap.OpenTicketInWebBrowserBatch): tasks := m.componenetTasksTable.GetSelectedTasks() @@ -249,6 +342,10 @@ func (m *Model) Update(msg tea.Msg) tea.Cmd { m.log.Debug("Toggle copy mode") m.copyMode = true + case key.Matches(msg, m.keyMap.EditMode): + m.log.Debug("Toggle edit mode") + m.editMode = true + case key.Matches(msg, m.keyMap.LostFocus): switch m.state { case m.componenetTasksSidebar.Id(): @@ -294,11 +391,59 @@ func (m *Model) Update(msg tea.Msg) tea.Cmd { m.componenetTasksTable.SetFocused(false) - if err := m.componenetTasksSidebar.SetTask(id); err != nil { + if err := m.componenetTasksSidebar.SelectTask(id); err != nil { cmds = append(cmds, common.ErrCmd(err)) } cmds = append(cmds, cmd) + + case common.EditorFinishedMsg: + err := msg.Err + id := msg.Id + if err := err; err != nil { + return common.ErrCmd(err) + } + + switch id { + case editorIdDescription: + data := msg.Data.(string) + m.componenetTasksSidebar.SelectedTask.Description = data + case editorIdName: + data := msg.Data.(string) + m.componenetTasksSidebar.SelectedTask.Name = data + case editorIdStatus: + data := msg.Data.(string) + m.componenetTasksSidebar.SelectedTask.Status.Status = data + } + + cmds = append(cmds, UpdateTaskCmd(m.componenetTasksSidebar.SelectedTask)) + + if err := m.componenetTasksSidebar.SetTask(m.componenetTasksSidebar.SelectedTask); err != nil { + return common.ErrCmd(err) + } + + tableTasks := m.componenetTasksTable.GetTasks() + tableTasks[m.componenetTasksTable.SelectedTaskIndex] = m.componenetTasksSidebar.SelectedTask + m.componenetTasksTable.SetTasks(tableTasks) + + case UpdateTaskMsg: + t, err := m.ctx.Api.UpdateTask(m.componenetTasksSidebar.SelectedTask) + if err != nil { + return common.ErrCmd(err) + } + + if err := m.componenetTasksSidebar.SetTask(t); err != nil { + return common.ErrCmd(err) + } + + tableTasks := m.componenetTasksTable.GetTasks() + tableTasks[m.componenetTasksTable.SelectedTaskIndex] = m.componenetTasksSidebar.SelectedTask + m.componenetTasksTable.SetTasks(tableTasks) + + // TODO: this is temp solution withouth err checking + // because we are not able to distinguish upstream + m.ctx.Api.SyncTasksFromView(m.SelectedViewListId) //nolint:errcheck + m.ctx.Api.SyncTasksFromList(m.SelectedViewListId) //nolint:errcheck } cmds = append(cmds, @@ -320,7 +465,7 @@ func (m *Model) SetTasks(tasks []clickup.Task) { // TODO: check if it should yield at all or move it to cmd id := tasks[0].Id - if err := m.componenetTasksSidebar.SetTask(id); err != nil { + if err := m.componenetTasksSidebar.SelectTask(id); err != nil { m.log.Fatal(err) } } @@ -330,10 +475,15 @@ func (m Model) View() string { if m.Focused { bColor = m.ctx.Theme.BordersColorActive } + if m.copyMode { bColor = m.ctx.Theme.BordersColorCopyMode } + if m.editMode { + bColor = m.ctx.Theme.BordersColorEditMode + } + style := lipgloss.NewStyle(). Width(m.size.Width). MaxWidth(m.size.Width). @@ -391,10 +541,15 @@ func (m Model) View() string { if m.componenetTasksTable.GetFocused() { tasksTableBorders = m.ctx.Theme.BordersColorActive } + if m.copyMode { tasksTableBorders = m.ctx.Theme.BordersColorCopyMode } + if m.editMode { + tasksTableBorders = m.ctx.Theme.BordersColorEditMode + } + tmpStyle := style. Inherit(styleBorders)