diff --git a/.gitignore b/.gitignore
index 05e0274..839d465 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,5 +13,6 @@
# Dependency directories (remove the comment below to include it)
# vendor/
+builds/
/geek-life
/geek-life.db
diff --git a/README.md b/README.md
index b196374..8ffc48f 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,135 @@
-# geek-task
-Todo List Manager for Geeks
+geek-life - The CLI Task Manager for Geeks :technologist:
+=========
+
+[](https://opensource.org/licenses/MIT)
+[](https://goreportcard.com/report/github.com/ajaxray/geek-life)
+
+
+:superhero: Command Line hero?
+:computer: Live with the dark terminal?
+:memo: Think in Markdown?
+
+**Finally!** A full featured task manager for YOU!
+
+
+
+### Highlights
+
+- For ninjas - do things faster with keyboard shortcuts
+- Markdown lovers, feel at :house:! You'll see markdown everywhere.
+- Full featured (almost) - Projects, Tasks, due-dates, task notes...
+- A <4MB app that takes <1% CPU and ~7MB memory 1 - how much lighter you can think?
+- Task note editor with markdown syntax highlighting2
+- Full mouse support
+
+### Roadmap
+- [x] Create Project
+- [x] Delete Project
+- [ ] Edit Project
+- [x] Create Task (under project)
+- [x] Set Task due date (as `dd-mm-yyyy`) with shortcut
+- [x] Set Task due date with quick input buttons (today, +1 day, -1 day)
+- [x] Tasklist items should indicate status (done, pending, overdue) using colors
+- [x] Shortcut for Adding new Project and Task
+- [x] Global shortcuts for jumping to Projects or Tasks panel anytime
+- [x] Cleanup all completed tasks of project
+- [x] Task note editor should syntax highlight (markdown) and line numbers
+- [x] Status bar for common shortcuts
+- [x] Status bar should display success/error message of actions
+- [x] Status bar may display quick tips based on focused element
+- [ ] Dynamic lists
+ - Today - Due Today and overdue
+ - Upcoming - Due in one week
+ - Someday - No due date
+- [ ] [Havitica](https://habitica.com/)3 integration - Use it as Habitica client or use Habitica for cloud backup
+- [ ] Time tracking
+
+### Ready for action (installing and running)
+
+It's just a single binary file, **no external dependencies**.
+Just download the appropriate version of [executable from latest release](https://github.com/ajaxray/geek-life/releases) for your OS.
+Then rename and give it permission to execute. For example
+```bash
+mv geek-life_linux-amd64 geek-life
+sudo chmod +x geek-life
+```
+
+If you want to install it globally (run from any directory of your system), put it in your systems $PATH directory.
+```bash
+sudo mv geek-life /usr/local/bin/geek-life
+```
+Done!
+
+## Keyboard shortcuts
+
+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.
+
+| Context | Shortcut | Action |
+|---|:---:|---|
+| Global | `p` | Go to Project list |
+| Global | `t` | Go to Task list |
+| Projects | `n` | New Project |
+| Tasks | `n` | New Task |
+| Tasks | `Esc` | Go back to Projects Pane |
+| Task Detail | `Esc` | Go back to Tasks Pane |
+| Task Detail | `Space` | Toggle task as done/pending |
+| Task Detail | `d` | Set Due date |
+| Task Detail | `↓`/`↑` | Scroll Up/Down the note editor |
+| Task Detail | `e` | Activate note editor for modification |
+| Active Note Editor | `Esc` | Deactivate note editor and save content |
+
+*Tips about using shortcuts efficiently:*
+
+- `Esc` will bring you a step back - to previous pane in most cases.
+- When you're in Project or Task list, use `↓`/`↑` to navigate the list.
+- When you're in Project or Task list `Enter` will load currently selected Project/Task.
+- After creating new Project, focus will automatically move to Tasks. Start adding tasks immediately by pressing `n`.
+- After creating new Task, focus will stay in "new task" input. So that you can add tasks quickly one after another.
+- After creating new Task, Press `Esc` when you're done creating tasks.
+
+## Building blocks
+
+- Made with :love: and [golang](https://golang.org/) 1.14 *(you don't need golang to run it)*
+- Designed with [tview](https://github.com/rivo/tview) - interactive widgets for terminal-based UI
+- Task Note editor made with [femto](https://github.com/pgavlin/femto)
+- Datastore is [storm](https://github.com/asdine/storm) - a powerful toolkit for [BoltDB](https://github.com/etcd-io/bbolt)
+
+### Contribute
+
+If you fix a bug or want to add/improve a feature,
+and it's alligned with the focus (merging with ease) of this tool,
+I will be glad to accept your PR. :)
+
+## You may ask...
+
+#### Where is the data stored? Can I change the location?
+
+By default, it will try to create a db file in you home directory.
+
+But as a geek, you may try to put it different location (e,g, in you dropbox for syncing).
+In that case, just mention `DB_FILE` as an environment variable.
+
+```bash
+DB_FILE=~/dropbox/geek-life/default.db geek-life
+```
+
+#### How can I suggest a feature?
+
+Just [post an issue](https://github.com/ajaxray/geek-life/issues/new) describing your desired feature/enhancement
+and select `feature` label.
+
+Also, incomplete features in the current roadmap will be found in issue list.
+You may :thumbsup: issues if you want to increase priority of a feature.
+
+---
+### Footnotes
+1. In my Macbook Air, 1.6 GHz Dual-Core Intel Core i5, RAM: 8 GB 1600 MHz DDR3
+2. Use [monakai](https://github.com/sickill/vim-monokai) color scheme for markdown syntax
+3. Habitica is a free habit and productivity app that treats your real life like a game
+
+---
+> "This is the Book about which there is no doubt, a guidance for those conscious of Allah" - [Al-Quran](http://quran.com)
diff --git a/app/cli.go b/app/cli.go
index d73b71c..d8d9418 100644
--- a/app/cli.go
+++ b/app/cli.go
@@ -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,
diff --git a/app/project_detail.go b/app/project_detail.go
new file mode 100644
index 0000000..4aa50f1
--- /dev/null
+++ b/app/project_detail.go
@@ -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))
+}
diff --git a/app/projects.go b/app/projects.go
index 1348fb6..6b328f1 100644
--- a/app/projects.go
+++ b/app/projects.go
@@ -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 {
diff --git a/app/task_detail.go b/app/task_detail.go
index 08cceb7..f7656db 100644
--- a/app/task_detail.go
+++ b/app/task_detail.go
@@ -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(" 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
diff --git a/app/tasks.go b/app/tasks.go
index 5016291..5c551d5 100644
--- a/app/tasks.go
+++ b/app/tasks.go
@@ -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"
}
diff --git a/app/util.go b/app/util.go
index c8c8e45..bac57f9 100644
--- a/app/util.go
+++ b/app/util.go
@@ -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)
diff --git a/build.sh b/build.sh
new file mode 100644
index 0000000..726bec4
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,5 @@
+env GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o builds/geek-life_darwin-amd64 ./app
+env GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o builds/geek-life_linux-amd64 ./app
+env GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o builds/geek-life_linux-arm64 ./app
+env GOOS=windows GOARCH=386 go build -ldflags="-s -w" -o builds/geek-life_windows-386.exe ./app
+upx builds/geek-life_*
\ No newline at end of file
diff --git a/builds/.gitkeep b/builds/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/go.mod b/go.mod
index ad23b4b..9ec9c81 100644
--- a/go.mod
+++ b/go.mod
@@ -6,9 +6,9 @@ require (
github.com/asdine/storm/v3 v3.2.0
github.com/gdamore/tcell v1.3.0
github.com/golang/protobuf v1.3.3 // indirect
+ github.com/mitchellh/go-homedir v1.1.0
github.com/pgavlin/femto v0.0.0-20191028012355-31a9964a50b5
github.com/rivo/tview v0.0.0-20200507165325-823f280c5426
- github.com/stretchr/testify v1.4.0 // indirect
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
)
diff --git a/go.sum b/go.sum
index ea8d25b..ac90dde 100644
--- a/go.sum
+++ b/go.sum
@@ -32,6 +32,8 @@ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
github.com/mattn/go-runewidth v0.0.5/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/pgavlin/femto v0.0.0-20191028012355-31a9964a50b5 h1:jDmnr/0bMWhOta4Rk9F0RAeSpdr71SLvI34Ooav/CYM=
github.com/pgavlin/femto v0.0.0-20191028012355-31a9964a50b5/go.mod h1:vBSdyXS0eulREXU3VHVmy2BnHekZx8HTO8oEMIe/I+M=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
diff --git a/repository/storm/task.go b/repository/storm/task.go
index f843232..7031959 100644
--- a/repository/storm/task.go
+++ b/repository/storm/task.go
@@ -64,5 +64,5 @@ func (t *taskRepository) UpdateField(task *model.Task, field string, value inter
}
func (t *taskRepository) Delete(task *model.Task) error {
- panic("implement me")
+ return t.DB.DeleteStruct(task)
}
diff --git a/screens/geek-life_v1.gif b/screens/geek-life_v1.gif
new file mode 100644
index 0000000..7e61c7e
Binary files /dev/null and b/screens/geek-life_v1.gif differ
diff --git a/util/util.go b/util/util.go
index 311323b..623455c 100644
--- a/util/util.go
+++ b/util/util.go
@@ -2,22 +2,51 @@ package util
import (
"fmt"
+ "io/ioutil"
"log"
+ "os"
+ "path"
"strconv"
"strings"
"time"
"github.com/asdine/storm/v3"
+ "github.com/mitchellh/go-homedir"
)
// ConnectStorm Create database connection
func ConnectStorm() *storm.DB {
- db, err := storm.Open(GetEnvStr("DB_FILE", "geek-life.db"))
- FatalIfError(err, "Could not connect Embedded Database File")
+ dbPath := GetEnvStr("DB_FILE", "")
+ var err error
+
+ if dbPath == "" {
+ // Try in home dir
+ dbPath, err = homedir.Expand("~/.geek-life/default.db")
+
+ // If home dir is not detected, try in system tmp dir
+ if err != nil {
+ f, _ := ioutil.TempFile("geek-life", "default.db")
+ dbPath = f.Name()
+ }
+ }
+
+ CreateDirIfNotExist(path.Dir(dbPath))
+
+ db, openErr := storm.Open(dbPath)
+ FatalIfError(openErr, "Could not connect Embedded Database File")
return db
}
+func CreateDirIfNotExist(dir string) {
+ if _, err := os.Stat(dir); os.IsNotExist(err) {
+ err = os.MkdirAll(dir, 0755)
+ if err != nil {
+ panic(err)
+ }
+ }
+}
+
// UnixToTime create time.Time from string timestamp
func UnixToTime(timestamp string) time.Time {
parts := strings.Split(timestamp, ".")