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)
|
[](https://opensource.org/licenses/MIT)
|
||||||
@@ -72,10 +72,13 @@ Done! *Manage your tasks your way!*
|
|||||||
|
|
||||||
## :keyboard: Keyboard shortcuts
|
## :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.
|
Some shortcuts are global, some are contextual.
|
||||||
Contextual shortcuts will be applied according to focused pane/element.
|
Contextual shortcuts will be applied according to focused pane/element.
|
||||||
You'll see a currently focused pane bordered with double line.
|
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.
|
In case writing in a text input (e,g, new project/task, due date), you have to `Enter` to submit/save.
|
||||||
|
|
||||||
| Context | Shortcut | Action |
|
| Context | Shortcut | Action |
|
||||||
|
|||||||
24
app/cli.go
24
app/cli.go
@@ -1,13 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/asdine/storm/v3"
|
"github.com/asdine/storm/v3"
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
"github.com/rivo/tview"
|
"github.com/rivo/tview"
|
||||||
|
|
||||||
"github.com/ajaxray/geek-life/model"
|
|
||||||
"github.com/ajaxray/geek-life/repository"
|
"github.com/ajaxray/geek-life/repository"
|
||||||
repo "github.com/ajaxray/geek-life/repository/storm"
|
repo "github.com/ajaxray/geek-life/repository/storm"
|
||||||
"github.com/ajaxray/geek-life/util"
|
"github.com/ajaxray/geek-life/util"
|
||||||
@@ -15,20 +12,18 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
app *tview.Application
|
app *tview.Application
|
||||||
newProject, newTask *tview.InputField
|
newTask *tview.InputField
|
||||||
projectList, taskList *tview.List
|
taskList *tview.List
|
||||||
projectPane, projectDetailPane *tview.Flex
|
projectDetailPane *tview.Flex
|
||||||
taskPane, taskDetailPane *tview.Flex
|
taskPane, taskDetailPane *tview.Flex
|
||||||
layout, contents *tview.Flex
|
layout, contents *tview.Flex
|
||||||
|
|
||||||
statusBar *StatusBar
|
statusBar *StatusBar
|
||||||
|
projectPane *ProjectPane
|
||||||
|
|
||||||
db *storm.DB
|
db *storm.DB
|
||||||
projectRepo repository.ProjectRepository
|
projectRepo repository.ProjectRepository
|
||||||
taskRepo repository.TaskRepository
|
taskRepo repository.TaskRepository
|
||||||
|
|
||||||
projects []model.Project
|
|
||||||
currentProject *model.Project
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -47,10 +42,10 @@ func main() {
|
|||||||
AddItem(titleText, 0, 2, false).
|
AddItem(titleText, 0, 2, false).
|
||||||
AddItem(cloudStatus, 0, 1, false)
|
AddItem(cloudStatus, 0, 1, false)
|
||||||
|
|
||||||
prepareProjectPane()
|
|
||||||
prepareProjectDetail()
|
|
||||||
prepareTaskPane()
|
|
||||||
statusBar = makeStatusBar(app)
|
statusBar = makeStatusBar(app)
|
||||||
|
projectPane = NewProjectPane(projectRepo)
|
||||||
|
prepareTaskPane()
|
||||||
|
prepareProjectDetail()
|
||||||
prepareDetailPane()
|
prepareDetailPane()
|
||||||
|
|
||||||
contents = tview.NewFlex().
|
contents = tview.NewFlex().
|
||||||
@@ -78,7 +73,7 @@ func setKeyboardShortcuts() *tview.Application {
|
|||||||
// Handle based on current focus. Handlers may modify event
|
// Handle based on current focus. Handlers may modify event
|
||||||
switch {
|
switch {
|
||||||
case projectPane.HasFocus():
|
case projectPane.HasFocus():
|
||||||
event = handleProjectPaneShortcuts(event)
|
event = projectPane.handleShortcuts(event)
|
||||||
case taskPane.HasFocus():
|
case taskPane.HasFocus():
|
||||||
event = handleTaskPaneShortcuts(event)
|
event = handleTaskPaneShortcuts(event)
|
||||||
case taskDetailPane.HasFocus():
|
case taskDetailPane.HasFocus():
|
||||||
@@ -93,7 +88,8 @@ func setKeyboardShortcuts() *tview.Application {
|
|||||||
app.SetFocus(taskPane)
|
app.SetFocus(taskPane)
|
||||||
case 'f':
|
case 'f':
|
||||||
// @TODO : Remove
|
// @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
|
return event
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func prepareProjectDetail() {
|
func prepareProjectDetail() {
|
||||||
deleteBtn := makeButton("Delete Project", deleteCurrentProject)
|
deleteBtn := makeButton("Delete Project", projectPane.removeActivateProject)
|
||||||
clearBtn := makeButton("Clear Completed Tasks", clearCompletedTasks)
|
clearBtn := makeButton("Clear Completed Tasks", clearCompletedTasks)
|
||||||
|
|
||||||
deleteBtn.SetBackgroundColor(tcell.ColorRed)
|
deleteBtn.SetBackgroundColor(tcell.ColorRed)
|
||||||
@@ -23,21 +23,7 @@ func prepareProjectDetail() {
|
|||||||
projectDetailPane.SetBorder(true).SetTitle("[::u]A[::-]ctions")
|
projectDetailPane.SetBorder(true).SetTitle("[::u]A[::-]ctions")
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteCurrentProject() {
|
// @TODO - Move to tasks pane
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearCompletedTasks() {
|
func clearCompletedTasks() {
|
||||||
count := 0
|
count := 0
|
||||||
for i, task := range tasks {
|
for i, task := range tasks {
|
||||||
|
|||||||
177
app/projects.go
177
app/projects.go
@@ -7,102 +7,163 @@ import (
|
|||||||
"github.com/asdine/storm/v3"
|
"github.com/asdine/storm/v3"
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
"github.com/rivo/tview"
|
"github.com/rivo/tview"
|
||||||
|
|
||||||
|
"github.com/ajaxray/geek-life/model"
|
||||||
|
"github.com/ajaxray/geek-life/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
func prepareProjectPane() {
|
type ProjectPane struct {
|
||||||
projectList = tview.NewList().ShowSecondaryText(false)
|
*tview.Flex
|
||||||
loadProjectList()
|
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
|
||||||
|
}
|
||||||
|
|
||||||
newProject = makeLightTextInput("+[New Project]").
|
func NewProjectPane(repo repository.ProjectRepository) *ProjectPane {
|
||||||
SetDoneFunc(func(key tcell.Key) {
|
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 {
|
switch key {
|
||||||
case tcell.KeyEnter:
|
case tcell.KeyEnter:
|
||||||
if len(newProject.GetText()) < 3 {
|
pane.addNewProject()
|
||||||
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:
|
case tcell.KeyEsc:
|
||||||
app.SetFocus(projectPane)
|
app.SetFocus(projectPane)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
projectPane = tview.NewFlex().SetDirection(tview.FlexRow).
|
pane.AddItem(pane.list, 0, 1, true).
|
||||||
AddItem(projectList, 0, 1, true).
|
AddItem(pane.newProject, 1, 0, false)
|
||||||
AddItem(newProject, 1, 0, false)
|
|
||||||
|
|
||||||
projectPane.SetBorder(true).SetTitle("[::u]P[::-]rojects")
|
pane.SetBorder(true).SetTitle("[::u]P[::-]rojects")
|
||||||
|
pane.loadListItems(false)
|
||||||
|
|
||||||
|
return &pane
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadProjectList() {
|
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
|
var err error
|
||||||
projects, err = projectRepo.GetAll()
|
pane.projects, err = pane.repo.GetAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
statusBar.showForSeconds("Could not load Projects: "+err.Error(), 5)
|
statusBar.showForSeconds("Could not load Projects: "+err.Error(), 5)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
projectList.AddItem("[::d]Dynamic Lists", "", 0, nil)
|
for i := range pane.projects {
|
||||||
projectList.AddItem("[::d]"+strings.Repeat(string(tcell.RuneS3), 25), "", 0, nil)
|
pane.addProjectToList(i, false)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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/
|
// 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() {
|
pane.list.AddItem("- "+pane.projects[i].Title, "", 0, func(idx int) func() {
|
||||||
return func() { loadProject(idx) }
|
return func() { pane.activateProject(idx) }
|
||||||
}(i))
|
}(i))
|
||||||
|
|
||||||
if selectItem {
|
if selectItem {
|
||||||
projectList.SetCurrentItem(projectList.GetItemCount() - 1)
|
pane.list.SetCurrentItem(-1)
|
||||||
loadProject(i)
|
pane.activateProject(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadProject(idx int) {
|
func (pane *ProjectPane) addSection(name string) {
|
||||||
currentProject = &projects[idx]
|
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()
|
taskList.Clear()
|
||||||
app.SetFocus(taskPane)
|
app.SetFocus(taskPane)
|
||||||
var err error
|
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)
|
statusBar.showForSeconds("[red::]Error: "+err.Error(), 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, task := range tasks {
|
for i, task := range tasks {
|
||||||
addTaskToList(task, i)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
task, err := taskRepo.Create(*currentProject, newTask.GetText(), "", "", 0)
|
task, err := taskRepo.Create(*projectPane.activeProject, newTask.GetText(), "", "", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
statusBar.showForSeconds("[red::]Could not create Task:"+err.Error(), 5)
|
statusBar.showForSeconds("[red::]Could not create Task:"+err.Error(), 5)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user