Prepared for first release

- Creating db file in home dir or DB_FILE env variable
- Updated README with usages details
- Added GIF screencast
This commit is contained in:
Anis Ahmad
2020-06-06 02:13:04 +06:00
parent 89624edcba
commit 1cb95b3ce2
15 changed files with 373 additions and 114 deletions

View File

@@ -14,21 +14,22 @@ import (
)
var (
app *tview.Application
newProject, newTask *tview.InputField
projectList, taskList *tview.List
projectPane, taskPane, detailPane *tview.Flex
layout, contents *tview.Flex
statusBar *tview.Pages
message *tview.TextView
shortcutsPage, messagePage string = "shortcuts", "message"
app *tview.Application
newProject, newTask *tview.InputField
projectList, taskList *tview.List
projectPane, projectDetailPane *tview.Flex
taskPane, taskDetailPane *tview.Flex
layout, contents *tview.Flex
statusBar *tview.Pages
message *tview.TextView
shortcutsPage, messagePage string = "shortcuts", "message"
db *storm.DB
projectRepo repository.ProjectRepository
taskRepo repository.TaskRepository
projects []model.Project
currentProject model.Project
currentProject *model.Project
)
func main() {
@@ -40,7 +41,7 @@ func main() {
projectRepo = repo.NewProjectRepository(db)
taskRepo = repo.NewTaskRepository(db)
titleText := tview.NewTextView().SetText("[lime::b]Geek-life [::-]- life management for geeks!").SetDynamicColors(true)
titleText := tview.NewTextView().SetText("[lime::b]Geek-life [::-]- Task Manager for geeks!").SetDynamicColors(true)
cloudStatus := tview.NewTextView().SetText("[::d]Cloud Sync: off").SetTextAlign(tview.AlignRight).SetDynamicColors(true)
titleBar := tview.NewFlex().
@@ -48,6 +49,7 @@ func main() {
AddItem(cloudStatus, 0, 1, false)
prepareProjectPane()
prepareProjectDetail()
prepareTaskPane()
prepareStatusBar()
prepareDetailPane()
@@ -55,21 +57,20 @@ func main() {
contents = tview.NewFlex().
AddItem(projectPane, 25, 1, true).
AddItem(taskPane, 0, 2, false)
//AddItem(detailPane, 0, 3, true)
layout = tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(titleBar, 2, 1, false).
AddItem(contents, 0, 2, true).
AddItem(statusBar, 1, 1, false)
setKeyboardShortcuts(projectPane, taskPane)
setKeyboardShortcuts()
if err := app.SetRoot(layout, true).EnableMouse(true).Run(); err != nil {
panic(err)
}
}
func setKeyboardShortcuts(projectPane *tview.Flex, taskPane *tview.Flex) *tview.Application {
func setKeyboardShortcuts() *tview.Application {
return app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if ignoreKeyEvt() {
return event
@@ -81,7 +82,7 @@ func setKeyboardShortcuts(projectPane *tview.Flex, taskPane *tview.Flex) *tview.
event = handleProjectPaneShortcuts(event)
case taskPane.HasFocus():
event = handleTaskPaneShortcuts(event)
case detailPane.HasFocus():
case taskDetailPane.HasFocus():
event = handleDetailPaneShortcuts(event)
}
@@ -110,9 +111,9 @@ func prepareStatusBar() {
tview.NewGrid().
SetColumns(0, 0, 0, 0).
SetRows(0).
AddItem(tview.NewTextView().SetText("Shortcuts: Alt+.(dot)"), 0, 0, 1, 1, 0, 0, false).
AddItem(tview.NewTextView().SetText("New Project: n").SetTextAlign(tview.AlignCenter), 0, 1, 1, 1, 0, 0, false).
AddItem(tview.NewTextView().SetText("New Task: t").SetTextAlign(tview.AlignCenter), 0, 2, 1, 1, 0, 0, false).
AddItem(tview.NewTextView().SetText("Navigate List: ↓/↑"), 0, 0, 1, 1, 0, 0, false).
AddItem(tview.NewTextView().SetText("New Task/Project: n").SetTextAlign(tview.AlignCenter), 0, 1, 1, 1, 0, 0, false).
AddItem(tview.NewTextView().SetText("Step back: Esc").SetTextAlign(tview.AlignCenter), 0, 2, 1, 1, 0, 0, false).
AddItem(tview.NewTextView().SetText("Quit: Ctrl+C").SetTextAlign(tview.AlignRight), 0, 3, 1, 1, 0, 0, false),
true,
true,

50
app/project_detail.go Normal file
View File

@@ -0,0 +1,50 @@
package main
import (
"fmt"
"github.com/gdamore/tcell"
"github.com/rivo/tview"
)
func prepareProjectDetail() {
deleteBtn := makeButton("Delete Project", deleteCurrentProject)
clearBtn := makeButton("Clear Completed Tasks", clearCompletedTasks)
deleteBtn.SetBackgroundColor(tcell.ColorRed)
projectDetailPane = tview.NewFlex().SetDirection(tview.FlexRow).
// AddItem(activeProjectName, 1, 1, false).
// AddItem(makeHorizontalLine(tcell.RuneS3, tcell.ColorGray), 1, 1, false).
AddItem(deleteBtn, 3, 1, false).
AddItem(blankCell, 1, 1, false).
AddItem(clearBtn, 3, 1, false).
AddItem(blankCell, 0, 1, false)
projectDetailPane.SetBorder(true).SetTitle("[::u]A[::-]ctions")
}
func deleteCurrentProject() {
if currentProject != nil && projectRepo.Delete(currentProject) == nil {
for i, _ := range tasks {
taskRepo.Delete(&tasks[i])
}
showMessage("Removed Project: " + currentProject.Title)
removeThirdCol()
taskList.Clear()
projectList.Clear()
loadProjectList()
}
}
func clearCompletedTasks() {
count := 0
for i, task := range tasks {
if task.Completed && taskRepo.Delete(&tasks[i]) == nil {
taskList.RemoveItem(i)
count++
}
}
showMessage(fmt.Sprintf("[yellow]%d tasks cleared!", count))
}

View File

@@ -2,6 +2,7 @@ package main
import (
"fmt"
"strings"
"github.com/asdine/storm/v3"
"github.com/gdamore/tcell"
@@ -9,17 +10,8 @@ import (
)
func prepareProjectPane() {
var err error
projects, err = projectRepo.GetAll()
if err != nil {
showMessage("Could not load Projects: " + err.Error())
}
projectList = tview.NewList().ShowSecondaryText(false)
for i := range projects {
addProjectToList(i, false)
}
loadProjectList()
newProject = makeLightTextInput("+[New Project]").
SetDoneFunc(func(key tcell.Key) {
@@ -29,7 +21,7 @@ func prepareProjectPane() {
if err != nil {
showMessage("[red::]Failed to create Project:" + err.Error())
} else {
showMessage(fmt.Sprintf("[green::]Project %s created. Press n to start adding new tasks.", newProject.GetText()))
showMessage(fmt.Sprintf("[yellow::]Project %s created. Press n to start adding new tasks.", newProject.GetText()))
projects = append(projects, project)
addProjectToList(len(projects)-1, true)
newProject.SetText("")
@@ -46,6 +38,30 @@ func prepareProjectPane() {
projectPane.SetBorder(true).SetTitle("[::u]P[::-]rojects")
}
func loadProjectList() {
var err error
projects, err = projectRepo.GetAll()
if err != nil {
showMessage("Could not load Projects: " + err.Error())
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)
}
projectList.SetCurrentItem(6) // Select Projects, as dynamic lists are not ready
}
func 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() {
@@ -53,28 +69,28 @@ func addProjectToList(i int, selectItem bool) {
}(i))
if selectItem {
projectList.SetCurrentItem(i)
projectList.SetCurrentItem(projectList.GetItemCount() - 1)
loadProject(i)
}
}
func loadProject(idx int) {
currentProject = projects[idx]
currentProject = &projects[idx]
taskList.Clear()
app.SetFocus(taskPane)
var err error
if tasks, err = taskRepo.GetAllByProject(currentProject); err != nil && err != storm.ErrNotFound {
if tasks, err = taskRepo.GetAllByProject(*currentProject); err != nil && err != storm.ErrNotFound {
showMessage("[red::]Error: " + err.Error())
}
for i, task := range tasks {
taskList.AddItem(makeTaskListingTitle(task), "", 0, func(taskidx int) func() {
return func() { loadTask(taskidx) }
}(i))
addTaskToList(task, i)
}
contents.RemoveItem(detailPane)
removeThirdCol()
projectDetailPane.SetTitle("[::b]" + currentProject.Title)
contents.AddItem(projectDetailPane, 25, 0, false)
}
func handleProjectPaneShortcuts(event *tcell.EventKey) *tcell.EventKey {

View File

@@ -12,10 +12,12 @@ import (
var (
taskName, taskDateDisplay *tview.TextView
editorHint *tview.TextView
taskDate *tview.InputField
taskDetailView *femto.View
taskStatusToggle *tview.Button
colorscheme femto.Colorscheme
blankCell = tview.NewTextView()
)
const dateLayoutISO = "2006-01-02"
@@ -23,51 +25,40 @@ const dateLayoutHuman = "02 Jan, Monday"
func prepareDetailPane() {
taskName = tview.NewTextView().SetDynamicColors(true)
hr := makeHorizontalLine(tview.BoxDrawingsLightHorizontal)
prepareDetailsEditor()
taskStatusToggle = makeButton("Complete", func() {}).SetLabelColor(tcell.ColorLightGray)
taskStatusToggle = makeButton("Complete", toggleActiveTaskStatus).SetLabelColor(tcell.ColorLightGray)
hint := tview.NewTextView().SetTextColor(tcell.ColorYellow).
SetText("press Enter to save changes, Esc to ignore")
toggleHint := tview.NewTextView().SetTextColor(tcell.ColorDimGray).SetText("<space> to toggle")
detailPane = tview.NewFlex().SetDirection(tview.FlexRow).
editorLabel := tview.NewFlex().
AddItem(tview.NewTextView().SetText("Task Not[::u]e[::-]:").SetDynamicColors(true), 0, 1, false).
AddItem(makeButton("edit", func() { activateEditor() }), 6, 0, false)
editorHint = tview.NewTextView().
SetText(" e to edit, ↓↑ to scroll").
SetTextColor(tcell.ColorDimGray)
editorHelp := tview.NewFlex().
AddItem(editorHint, 0, 1, false).
AddItem(tview.NewTextView().SetTextAlign(tview.AlignRight).
SetText("syntax:markdown theme:monakai").
SetTextColor(tcell.ColorDimGray), 0, 1, false)
taskDetailPane = tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(taskName, 2, 1, true).
AddItem(hr, 1, 1, false).
AddItem(nil, 1, 1, false).
AddItem(makeHorizontalLine(tcell.RuneS3, tcell.ColorGray), 1, 1, false).
AddItem(blankCell, 1, 1, false).
AddItem(makeDateRow(), 1, 1, true).
AddItem(blankCell, 1, 1, false).
AddItem(editorLabel, 1, 1, false).
AddItem(taskDetailView, 15, 4, false).
AddItem(tview.NewTextView(), 1, 1, false).
AddItem(hint, 1, 1, false).
AddItem(nil, 0, 1, false).
AddItem(editorHelp, 1, 1, false).
AddItem(blankCell, 0, 1, false).
AddItem(toggleHint, 1, 1, false).
AddItem(taskStatusToggle, 3, 1, false)
detailPane.SetBorder(true).SetTitle("Detail")
// taskName is the default focus attracting child of detailPane
taskName.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyEsc:
app.SetFocus(taskPane)
case tcell.KeyDown:
taskDetailView.ScrollDown(1)
case tcell.KeyUp:
taskDetailView.ScrollUp(1)
case tcell.KeyRune:
// switch event.Rune() {
// case 'n':
// app.SetFocus(projectPane)
// case 'e':
// if detailPane.HasFocus() {
// activateEditor()
// }
// }
}
return event
})
taskDetailPane.SetBorder(true).SetTitle("Task Detail")
}
func makeDateRow() *tview.Flex {
@@ -83,6 +74,7 @@ func makeDateRow() *tview.Flex {
case tcell.KeyEsc:
setTaskDate(currentTask.DueDate, false)
}
app.SetFocus(taskDetailPane)
})
todaySelector := func() {
@@ -100,31 +92,28 @@ func makeDateRow() *tview.Flex {
return tview.NewFlex().
AddItem(taskDateDisplay, 0, 2, true).
AddItem(taskDate, 14, 0, true).
AddItem(nil, 1, 0, false).
AddItem(nil, 1, 0, false).
AddItem(blankCell, 1, 0, false).
AddItem(makeButton("today", todaySelector), 8, 1, false).
AddItem(nil, 1, 0, false).
AddItem(blankCell, 1, 0, false).
AddItem(makeButton("+1", nextDaySelector), 4, 1, false).
AddItem(nil, 1, 0, false).
AddItem(blankCell, 1, 0, false).
AddItem(makeButton("-1", prevDaySelector), 4, 1, false)
}
func setStatusToggle(idx int) {
action := func(i int, label string, color tcell.Color, status bool) {
taskStatusToggle.SetLabel(label).SetBackgroundColor(color)
taskStatusToggle.SetSelectedFunc(func() {
if taskRepo.UpdateField(currentTask, "Completed", status) == nil {
currentTask.Completed = status
loadTask(i)
taskList.SetItemText(i, makeTaskListingTitle(*currentTask), "")
}
})
}
func setStatusToggle() {
if currentTask.Completed {
action(idx, "Resume", tcell.ColorMaroon, false)
taskStatusToggle.SetLabel("Resume").SetBackgroundColor(tcell.ColorMaroon)
} else {
action(idx, "Complete", tcell.ColorDarkGreen, true)
taskStatusToggle.SetLabel("Complete").SetBackgroundColor(tcell.ColorDarkGreen)
}
}
func toggleActiveTaskStatus() {
status := !currentTask.Completed
if taskRepo.UpdateField(currentTask, "Completed", status) == nil {
currentTask.Completed = status
loadTask(currentTaskIdx)
taskList.SetItemText(currentTaskIdx, makeTaskListingTitle(*currentTask), "")
}
}
@@ -202,21 +191,34 @@ func makeBufferFromString(content string) *femto.Buffer {
func activateEditor() {
taskDetailView.Readonly = false
taskDetailView.SetBorderColor(tcell.ColorDarkOrange)
editorHint.SetText(" Esc to save changes")
app.SetFocus(taskDetailView)
}
func deactivateEditor() {
taskDetailView.Readonly = true
taskDetailView.SetBorderColor(tcell.ColorLightSlateGray)
app.SetFocus(detailPane)
editorHint.SetText(" e to edit, ↓↑ to scroll")
app.SetFocus(taskDetailPane)
}
func handleDetailPaneShortcuts(event *tcell.EventKey) *tcell.EventKey {
switch event.Rune() {
case 'e':
activateEditor()
case 'd':
app.SetFocus(taskDate)
switch event.Key() {
case tcell.KeyEsc:
app.SetFocus(taskPane)
case tcell.KeyDown:
taskDetailView.ScrollDown(1)
case tcell.KeyUp:
taskDetailView.ScrollUp(1)
case tcell.KeyRune:
switch event.Rune() {
case 'e':
activateEditor()
case 'd':
app.SetFocus(taskDate)
case ' ':
toggleActiveTaskStatus()
}
}
return event

View File

@@ -11,8 +11,9 @@ import (
)
var (
tasks []model.Task
currentTask *model.Task
tasks []model.Task
currentTask *model.Task
currentTaskIdx int
)
func prepareTaskPane() {
@@ -25,12 +26,13 @@ func prepareTaskPane() {
SetDoneFunc(func(key tcell.Key) {
switch key {
case tcell.KeyEnter:
task, err := taskRepo.Create(currentProject, newTask.GetText(), "", "", time.Now().Unix())
task, err := taskRepo.Create(*currentProject, newTask.GetText(), "", "", 0)
if err != nil {
showMessage("[red::]Could not create Task:" + err.Error())
}
taskList.AddItem(task.Title, "", 0, nil)
tasks = append(tasks, task)
addTaskToList(task, len(tasks)-1)
newTask.SetText("")
case tcell.KeyEsc:
app.SetFocus(taskPane)
@@ -45,26 +47,38 @@ func prepareTaskPane() {
taskPane.SetBorder(true).SetTitle("[::u]T[::-]asks")
}
func addTaskToList(task model.Task, i int) *tview.List {
return taskList.AddItem(makeTaskListingTitle(task), "", 0, func(taskidx int) func() {
return func() { loadTask(taskidx) }
}(i))
}
func loadTask(idx int) {
contents.RemoveItem(detailPane)
currentTask = &tasks[idx]
removeThirdCol()
currentTaskIdx = idx
currentTask = &tasks[currentTaskIdx]
taskName.SetText(fmt.Sprintf("[%s::b]# %s", getTaskTitleColor(*currentTask), currentTask.Title))
taskDetailView.Buf = makeBufferFromString(currentTask.Details)
taskDetailView.SetColorscheme(colorscheme)
taskDetailView.Start()
setTaskDate(currentTask.DueDate, false)
setStatusToggle()
contents.AddItem(detailPane, 0, 3, false)
setStatusToggle(idx)
contents.AddItem(taskDetailPane, 0, 3, false)
deactivateEditor()
}
func removeThirdCol() {
contents.RemoveItem(taskDetailPane)
contents.RemoveItem(projectDetailPane)
}
func getTaskTitleColor(task model.Task) string {
colorName := "whitesmoke"
colorName := "olive"
if task.Completed {
colorName = "lime"
} else if task.DueDate != 0 && task.DueDate < time.Now().Unix() {
} else if task.DueDate != 0 && task.DueDate < time.Now().Truncate(24*time.Hour).Unix() {
colorName = "red"
}

View File

@@ -1,6 +1,7 @@
package main
import (
"fmt"
"reflect"
"time"
@@ -10,11 +11,11 @@ import (
"github.com/ajaxray/geek-life/util"
)
func makeHorizontalLine(lineChar rune) *tview.TextView {
func makeHorizontalLine(lineChar rune, color tcell.Color) *tview.TextView {
hr := tview.NewTextView()
hr.SetDrawFunc(func(screen tcell.Screen, x int, y int, width int, height int) (int, int, int, int) {
// Draw a horizontal line across the middle of the box.
style := tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorBlack)
style := tcell.StyleDefault.Foreground(color).Background(tcell.ColorBlack)
centerY := y + height/2
for cx := x; cx < x+width; cx++ {
screen.SetContent(cx, centerY, lineChar, nil, style)
@@ -30,7 +31,7 @@ func makeHorizontalLine(lineChar rune) *tview.TextView {
func makeLightTextInput(placeholder string) *tview.InputField {
return tview.NewInputField().
SetPlaceholder(placeholder).
SetPlaceholderTextColor(tcell.ColorLightSlateGray).
SetPlaceholderTextColor(tcell.ColorYellow).
SetFieldTextColor(tcell.ColorBlack).
SetFieldBackgroundColor(tcell.ColorGray)
}
@@ -49,13 +50,18 @@ func showMessage(text string) {
statusBar.SwitchToPage(messagePage)
go func() {
time.Sleep(time.Second * 5)
app.QueueUpdateDraw(func() {
time.Sleep(time.Second * 5)
statusBar.SwitchToPage(shortcutsPage)
})
}()
}
func yetToImplement(feature string) func() {
message := fmt.Sprintf("[yellow]%s is yet to implement. Please Check in next version.", feature)
return func() { showMessage(message) }
}
func makeButton(label string, handler func()) *tview.Button {
btn := tview.NewButton(label).SetSelectedFunc(handler).
SetLabelColor(tcell.ColorWhite)