Refactored Projects pane - changed into a type
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
The CLI Task Manager for Geeks :technologist:
|
||||
The CLI To-Do List / Task Manager for Geeks :technologist:
|
||||
=========
|
||||
|
||||
[](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, "<u>P</u>roject" 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.
|
||||
|
||||
|
||||
32
app/cli.go
32
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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
195
app/projects.go
195
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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user