From 3cc13ed445349b6cec61d2437e3b8817b065ef48 Mon Sep 17 00:00:00 2001 From: Anis Ahmad Date: Tue, 9 Jun 2020 03:43:18 +0600 Subject: [PATCH] Refactored Projects pane - changed into a type --- README.md | 5 +- app/cli.go | 32 +++---- app/project_detail.go | 18 +--- app/projects.go | 195 +++++++++++++++++++++++++++--------------- app/tasks.go | 2 +- 5 files changed, 149 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 6267efb..4caf71a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -The CLI Task Manager for Geeks :technologist: +The CLI To-Do List / Task Manager for Geeks :technologist: ========= [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) @@ -72,9 +72,12 @@ Done! *Manage your tasks your way!* ## :keyboard: Keyboard shortcuts +Shortcut key for a pane/element will be marked with underline (e,g, "Project" as project pane title). + Some shortcuts are global, some are contextual. Contextual shortcuts will be applied according to focused pane/element. You'll see a currently focused pane bordered with double line. + In case writing in a text input (e,g, new project/task, due date), you have to `Enter` to submit/save. diff --git a/app/cli.go b/app/cli.go index 353e3d5..e35e40f 100644 --- a/app/cli.go +++ b/app/cli.go @@ -1,34 +1,29 @@ package main import ( - "reflect" - "github.com/asdine/storm/v3" "github.com/gdamore/tcell" "github.com/rivo/tview" - "github.com/ajaxray/geek-life/model" "github.com/ajaxray/geek-life/repository" repo "github.com/ajaxray/geek-life/repository/storm" "github.com/ajaxray/geek-life/util" ) var ( - app *tview.Application - newProject, newTask *tview.InputField - projectList, taskList *tview.List - projectPane, projectDetailPane *tview.Flex - taskPane, taskDetailPane *tview.Flex - layout, contents *tview.Flex + app *tview.Application + newTask *tview.InputField + taskList *tview.List + projectDetailPane *tview.Flex + taskPane, taskDetailPane *tview.Flex + layout, contents *tview.Flex - statusBar *StatusBar + statusBar *StatusBar + projectPane *ProjectPane db *storm.DB projectRepo repository.ProjectRepository taskRepo repository.TaskRepository - - projects []model.Project - currentProject *model.Project ) func main() { @@ -47,10 +42,10 @@ func main() { AddItem(titleText, 0, 2, false). AddItem(cloudStatus, 0, 1, false) - prepareProjectPane() - prepareProjectDetail() - prepareTaskPane() statusBar = makeStatusBar(app) + projectPane = NewProjectPane(projectRepo) + prepareTaskPane() + prepareProjectDetail() prepareDetailPane() contents = tview.NewFlex(). @@ -78,7 +73,7 @@ func setKeyboardShortcuts() *tview.Application { // Handle based on current focus. Handlers may modify event switch { case projectPane.HasFocus(): - event = handleProjectPaneShortcuts(event) + event = projectPane.handleShortcuts(event) case taskPane.HasFocus(): event = handleTaskPaneShortcuts(event) case taskDetailPane.HasFocus(): @@ -93,7 +88,8 @@ func setKeyboardShortcuts() *tview.Application { app.SetFocus(taskPane) case 'f': // @TODO : Remove - statusBar.showForSeconds(reflect.TypeOf(app.GetFocus()).String(), 5) + //statusBar.showForSeconds(reflect.TypeOf(app.GetFocus()).String(), 5) + statusBar.showForSeconds(projectPane.activeProject.Title, 5) } return event diff --git a/app/project_detail.go b/app/project_detail.go index b40fc4a..30e0a06 100644 --- a/app/project_detail.go +++ b/app/project_detail.go @@ -8,7 +8,7 @@ import ( ) func prepareProjectDetail() { - deleteBtn := makeButton("Delete Project", deleteCurrentProject) + deleteBtn := makeButton("Delete Project", projectPane.removeActivateProject) clearBtn := makeButton("Clear Completed Tasks", clearCompletedTasks) deleteBtn.SetBackgroundColor(tcell.ColorRed) @@ -23,21 +23,7 @@ func prepareProjectDetail() { projectDetailPane.SetBorder(true).SetTitle("[::u]A[::-]ctions") } -func deleteCurrentProject() { - if currentProject != nil && projectRepo.Delete(currentProject) == nil { - for i := range tasks { - _ = taskRepo.Delete(&tasks[i]) - } - - statusBar.showForSeconds("[lime]Removed Project: "+currentProject.Title, 5) - removeThirdCol() - taskList.Clear() - projectList.Clear() - - loadProjectList() - } -} - +// @TODO - Move to tasks pane func clearCompletedTasks() { count := 0 for i, task := range tasks { diff --git a/app/projects.go b/app/projects.go index 1e8c617..ad7c85b 100644 --- a/app/projects.go +++ b/app/projects.go @@ -7,102 +7,163 @@ import ( "github.com/asdine/storm/v3" "github.com/gdamore/tcell" "github.com/rivo/tview" + + "github.com/ajaxray/geek-life/model" + "github.com/ajaxray/geek-life/repository" ) -func prepareProjectPane() { - projectList = tview.NewList().ShowSecondaryText(false) - loadProjectList() - - newProject = makeLightTextInput("+[New Project]"). - SetDoneFunc(func(key tcell.Key) { - switch key { - case tcell.KeyEnter: - if len(newProject.GetText()) < 3 { - statusBar.showForSeconds("[red::]Project name should be at least 3 character", 5) - return - } - - project, err := projectRepo.Create(newProject.GetText(), "") - if err != nil { - statusBar.showForSeconds("[red::]Failed to create Project:"+err.Error(), 5) - } else { - statusBar.showForSeconds(fmt.Sprintf("[yellow::]Project %s created. Press n to start adding new tasks.", newProject.GetText()), 5) - projects = append(projects, project) - addProjectToList(len(projects)-1, true) - newProject.SetText("") - } - case tcell.KeyEsc: - app.SetFocus(projectPane) - } - }) - - projectPane = tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(projectList, 0, 1, true). - AddItem(newProject, 1, 0, false) - - projectPane.SetBorder(true).SetTitle("[::u]P[::-]rojects") +type ProjectPane struct { + *tview.Flex + projects []model.Project + list *tview.List + newProject *tview.InputField + repo repository.ProjectRepository + activeProject *model.Project + projectListStarting int // The index in list where project names starts } -func loadProjectList() { +func NewProjectPane(repo repository.ProjectRepository) *ProjectPane { + pane := ProjectPane{ + Flex: tview.NewFlex().SetDirection(tview.FlexRow), + list: tview.NewList().ShowSecondaryText(false), + newProject: makeLightTextInput("+[New Project]"), + repo: repo, + } + + pane.newProject.SetDoneFunc(func(key tcell.Key) { + switch key { + case tcell.KeyEnter: + pane.addNewProject() + case tcell.KeyEsc: + app.SetFocus(projectPane) + } + }) + + pane.AddItem(pane.list, 0, 1, true). + AddItem(pane.newProject, 1, 0, false) + + pane.SetBorder(true).SetTitle("[::u]P[::-]rojects") + pane.loadListItems(false) + + return &pane +} + +func (pane *ProjectPane) addNewProject() { + name := pane.newProject.GetText() + if len(name) < 3 { + statusBar.showForSeconds("[red::]Project name should be at least 3 character", 5) + return + } + + project, err := pane.repo.Create(name, "") + if err != nil { + statusBar.showForSeconds("[red::]Failed to create Project:"+err.Error(), 5) + } else { + statusBar.showForSeconds(fmt.Sprintf("[yellow::]Project %s created. Press n to start adding new tasks.", name), 5) + pane.projects = append(pane.projects, project) + pane.addProjectToList(len(pane.projects)-1, true) + pane.newProject.SetText("") + } +} + +func (pane *ProjectPane) addDynamicLists() { + pane.addSection("Dynamic Lists") + pane.list.AddItem("- Today", "", 0, yetToImplement("Today's Tasks")) + pane.list.AddItem("- Upcoming", "", 0, yetToImplement("Upcoming Tasks")) + pane.list.AddItem("- No Due Date", "", 0, yetToImplement("Unscheduled Tasks")) +} + +func (pane *ProjectPane) addProjectList() { + pane.addSection("Projects") + pane.projectListStarting = pane.list.GetItemCount() + var err error - projects, err = projectRepo.GetAll() + pane.projects, err = pane.repo.GetAll() if err != nil { statusBar.showForSeconds("Could not load Projects: "+err.Error(), 5) return } - projectList.AddItem("[::d]Dynamic Lists", "", 0, nil) - projectList.AddItem("[::d]"+strings.Repeat(string(tcell.RuneS3), 25), "", 0, nil) - projectList.AddItem("- Today", "", 0, yetToImplement("Today's Tasks")) - projectList.AddItem("- Upcoming", "", 0, yetToImplement("Upcoming Tasks")) - projectList.AddItem("- No Due Date", "", 0, yetToImplement("Unscheduled Tasks")) - projectList.AddItem("", "", 0, nil) - projectList.AddItem("[::d]Projects", "", 0, nil) - projectList.AddItem("[::d]"+strings.Repeat(string(tcell.RuneS3), 25), "", 0, nil) - - for i := range projects { - addProjectToList(i, false) + for i := range pane.projects { + pane.addProjectToList(i, false) } - projectList.SetCurrentItem(6) // Select Projects, as dynamic lists are not ready + pane.list.SetCurrentItem(pane.projectListStarting) } -func addProjectToList(i int, selectItem bool) { +func (pane *ProjectPane) addProjectToList(i int, selectItem bool) { // To avoid overriding of loop variables - https://www.calhoun.io/gotchas-and-common-mistakes-with-closures-in-go/ - projectList.AddItem("- "+projects[i].Title, "", 0, func(idx int) func() { - return func() { loadProject(idx) } + pane.list.AddItem("- "+pane.projects[i].Title, "", 0, func(idx int) func() { + return func() { pane.activateProject(idx) } }(i)) if selectItem { - projectList.SetCurrentItem(projectList.GetItemCount() - 1) - loadProject(i) + pane.list.SetCurrentItem(-1) + pane.activateProject(i) } } -func loadProject(idx int) { - currentProject = &projects[idx] +func (pane *ProjectPane) addSection(name string) { + pane.list.AddItem("[::d]"+name, "", 0, nil) + pane.list.AddItem("[::d]"+strings.Repeat(string(tcell.RuneS3), 25), "", 0, nil) +} + +func (pane *ProjectPane) handleShortcuts(event *tcell.EventKey) *tcell.EventKey { + switch event.Rune() { + case 'n': + app.SetFocus(pane.newProject) + } + + return event +} + +func (pane *ProjectPane) activateProject(idx int) { + pane.activeProject = &pane.projects[idx] + loadProjectTasks() + + removeThirdCol() + projectDetailPane.SetTitle("[::b]" + pane.activeProject.Title) + contents.AddItem(projectDetailPane, 25, 0, false) +} + +func (pane *ProjectPane) removeActivateProject() { + if pane.activeProject != nil && pane.repo.Delete(pane.activeProject) == nil { + + // @TODO - Move to tasks pane + for i := range tasks { + _ = taskRepo.Delete(&tasks[i]) + } + + statusBar.showForSeconds("[lime]Removed Project: "+pane.activeProject.Title, 5) + removeThirdCol() + taskList.Clear() + + pane.loadListItems(true) + } +} + +func (pane *ProjectPane) loadListItems(focus bool) { + pane.list.Clear() + pane.addDynamicLists() + pane.list.AddItem("", "", 0, nil) + pane.addProjectList() + + if focus { + app.SetFocus(pane) + } +} + +// @TODO - Should be broken down into respective pane +func loadProjectTasks() { taskList.Clear() app.SetFocus(taskPane) var err error - if tasks, err = taskRepo.GetAllByProject(*currentProject); err != nil && err != storm.ErrNotFound { + if tasks, err = taskRepo.GetAllByProject(*projectPane.activeProject); err != nil && err != storm.ErrNotFound { statusBar.showForSeconds("[red::]Error: "+err.Error(), 5) } for i, task := range tasks { addTaskToList(task, i) } - - removeThirdCol() - projectDetailPane.SetTitle("[::b]" + currentProject.Title) - contents.AddItem(projectDetailPane, 25, 0, false) -} - -func handleProjectPaneShortcuts(event *tcell.EventKey) *tcell.EventKey { - switch event.Rune() { - case 'n': - app.SetFocus(newProject) - } - - return event } diff --git a/app/tasks.go b/app/tasks.go index 2822945..da584e1 100644 --- a/app/tasks.go +++ b/app/tasks.go @@ -31,7 +31,7 @@ func prepareTaskPane() { return } - task, err := taskRepo.Create(*currentProject, newTask.GetText(), "", "", 0) + task, err := taskRepo.Create(*projectPane.activeProject, newTask.GetText(), "", "", 0) if err != nil { statusBar.showForSeconds("[red::]Could not create Task:"+err.Error(), 5) }