From 30cd7f40a149de352b2d1fd05a3ac153ad68060c Mon Sep 17 00:00:00 2001 From: Anis Ahmad Date: Sat, 30 May 2020 01:30:26 +0600 Subject: [PATCH] Creating Projects, Tasks and editing Task details, done/resume working. --- .gitignore | 2 + app/cli.go | 121 ++++++++++++++++++ app/projects.go | 78 ++++++++++++ app/task_detail.go | 183 +++++++++++++++++++++++++++ app/tasks.go | 84 +++++++++++++ app/tui.go | 243 ------------------------------------ app/util.go | 62 +++++++++ go.mod | 1 + go.sum | 12 ++ repository/project.go | 1 + repository/storm/project.go | 4 + repository/storm/task.go | 22 ++-- repository/task.go | 5 +- 13 files changed, 564 insertions(+), 254 deletions(-) create mode 100644 app/cli.go create mode 100644 app/projects.go create mode 100644 app/task_detail.go create mode 100644 app/tasks.go delete mode 100644 app/tui.go create mode 100644 app/util.go diff --git a/.gitignore b/.gitignore index 66fd13c..05e0274 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ # Dependency directories (remove the comment below to include it) # vendor/ +/geek-life +/geek-life.db diff --git a/app/cli.go b/app/cli.go new file mode 100644 index 0000000..9fba393 --- /dev/null +++ b/app/cli.go @@ -0,0 +1,121 @@ +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, taskPane, detailPane *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 +) + +func main() { + app = tview.NewApplication() + + db = util.ConnectStorm() + defer db.Close() + + projectRepo = repo.NewProjectRepository(db) + taskRepo = repo.NewTaskRepository(db) + + titleText := tview.NewTextView().SetText("[lime::b]Geek-life [::-]- life management for geeks!").SetDynamicColors(true) + cloudStatus := tview.NewTextView().SetText("[::d]Cloud Sync: off").SetTextAlign(tview.AlignRight).SetDynamicColors(true) + + titleBar := tview.NewFlex(). + AddItem(titleText, 0, 2, false). + AddItem(cloudStatus, 0, 1, false) + + prepareProjectPane() + prepareTaskPane() + prepareStatusBar() + prepareDetailPane() + + 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) + + if err := app.SetRoot(layout, true).EnableMouse(true).Run(); err != nil { + panic(err) + } +} + +func setKeyboardShortcuts(projectPane *tview.Flex, taskPane *tview.Flex) *tview.Application { + return app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if ignoreKeyEvt() { + return event + } + switch event.Rune() { + case 'p': + app.SetFocus(projectPane) + case 't': + app.SetFocus(taskPane) + case 'n': + if projectPane.HasFocus() { + app.SetFocus(newProject) + } else if taskPane.HasFocus() { + app.SetFocus(newTask) + } + case 'e': + if detailPane.HasFocus() { + activateEditor() + } else { + // @TODO : Remove + showMessage(reflect.TypeOf(app.GetFocus()).String()) + } + case 'f': + // @TODO : Remove + showMessage(reflect.TypeOf(app.GetFocus()).String()) + } + + return event + }) +} + +func prepareStatusBar() { + statusBar = tview.NewPages() + + message = tview.NewTextView().SetDynamicColors(true).SetText("Loading...") + statusBar.AddPage(messagePage, message, true, true) + + statusBar.AddPage(shortcutsPage, + 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("Quit: Ctrl+C").SetTextAlign(tview.AlignRight), 0, 3, 1, 1, 0, 0, false), + true, + true, + ) +} diff --git a/app/projects.go b/app/projects.go new file mode 100644 index 0000000..dc8c0e0 --- /dev/null +++ b/app/projects.go @@ -0,0 +1,78 @@ +package main + +import ( + "fmt" + + "github.com/asdine/storm/v3" + "github.com/gdamore/tcell" + "github.com/rivo/tview" +) + +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) + } + + newProject = makeLightTextInput("+[New Project]"). + SetDoneFunc(func(key tcell.Key) { + switch key { + case tcell.KeyEnter: + project, err := projectRepo.Create(newProject.GetText(), "") + 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())) + 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") +} + +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() { + return func() { loadProject(idx) } + }(i)) + + if selectItem { + projectList.SetCurrentItem(i) + loadProject(i) + } +} + +func loadProject(idx int) { + currentProject = projects[idx] + taskList.Clear() + app.SetFocus(taskPane) + var err error + + 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)) + } + + contents.RemoveItem(detailPane) +} diff --git a/app/task_detail.go b/app/task_detail.go new file mode 100644 index 0000000..235f5a8 --- /dev/null +++ b/app/task_detail.go @@ -0,0 +1,183 @@ +package main + +import ( + "time" + + "github.com/gdamore/tcell" + "github.com/pgavlin/femto" + "github.com/pgavlin/femto/runtime" + "github.com/rivo/tview" +) + +var ( + taskName *tview.TextView + taskDate *tview.InputField + taskDetailView *femto.View + taskStatusToggle *tview.Button + colorscheme femto.Colorscheme +) + +const dateLayoutISO = "2006-01-02" + +func prepareDetailPane() { + taskName = tview.NewTextView().SetDynamicColors(true) + hr := makeHorizontalLine(tview.BoxDrawingsLightHorizontal) + + prepareDetailsEditor() + + taskStatusToggle = makeButton("Complete", func() {}).SetLabelColor(tcell.ColorLightGray) + + hint := tview.NewTextView().SetTextColor(tcell.ColorYellow). + SetText("press Enter to save changes, Esc to ignore") + + detailPane = tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(taskName, 2, 1, true). + AddItem(hr, 1, 1, false). + AddItem(nil, 1, 1, false). + AddItem(makeDateRow(), 1, 1, true). + AddItem(taskDetailView, 15, 4, false). + AddItem(tview.NewTextView(), 1, 1, false). + AddItem(hint, 1, 1, false). + AddItem(nil, 0, 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 + }) +} + +func makeDateRow() *tview.Flex { + taskDate = makeLightTextInput("yyyy-mm-dd"). + SetLabel("Due Date: "). + SetLabelColor(tcell.ColorGray). + SetFieldWidth(12) + + todaySelector := func() { + taskDate.SetText(time.Now().Format(dateLayoutISO)) + } + + nextDaySelector := func() { + currentText := taskDate.GetText() + if date, err := time.Parse(dateLayoutISO, currentText); err == nil { + taskDate.SetText(date.AddDate(0, 0, 1).Format(dateLayoutISO)) + } else { + taskDate.SetText(time.Now().AddDate(0, 0, 1).Format(dateLayoutISO)) + } + } + + prevDaySelector := func() { + currentText := taskDate.GetText() + if date, err := time.Parse(dateLayoutISO, currentText); err == nil { + taskDate.SetText(date.AddDate(0, 0, -1).Format(dateLayoutISO)) + } else { + taskDate.SetText(time.Now().AddDate(0, 0, -1).Format(dateLayoutISO)) + } + } + + return tview.NewFlex(). + AddItem(taskDate, 25, 2, true). + AddItem(nil, 1, 0, false). + AddItem(makeButton("today", todaySelector), 8, 1, false). + AddItem(nil, 1, 0, false). + AddItem(makeButton("+1", nextDaySelector), 4, 1, false). + AddItem(nil, 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), "") + } + }) + } + + if currentTask.Completed { + action(idx, "Resume", tcell.ColorMaroon, false) + } else { + action(idx, "Complete", tcell.ColorDarkGreen, true) + } +} + +func prepareDetailsEditor() { + taskDetailView = femto.NewView(makeBufferFromString("")) + taskDetailView.SetRuntimeFiles(runtime.Files) + + // var colorscheme femto.Colorscheme + if monokai := runtime.Files.FindFile(femto.RTColorscheme, "monokai"); monokai != nil { + if data, err := monokai.Data(); err == nil { + colorscheme = femto.ParseColorscheme(string(data)) + } + } + taskDetailView.SetColorscheme(colorscheme) + taskDetailView.SetBorder(true) + taskDetailView.SetBorderColor(tcell.ColorLightSlateGray) + + taskDetailView.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + switch event.Key() { + case tcell.KeyEsc: + currentTask.Details = taskDetailView.Buf.String() + err := taskRepo.Update(currentTask) + if err == nil { + showMessage("[lime]Saved task detail") + } else { + showMessage("[red]Could not save: " + err.Error()) + } + + deactivateEditor() + return nil + } + + return event + }) +} + +func makeBufferFromString(content string) *femto.Buffer { + buff := femto.NewBufferFromString(content, "") + // taskDetail.Settings["ruler"] = false + buff.Settings["filetype"] = "markdown" + buff.Settings["keepautoindent"] = true + buff.Settings["statusline"] = false + buff.Settings["softwrap"] = true + buff.Settings["scrollbar"] = true + + return buff +} + +func activateEditor() { + taskDetailView.Readonly = false + taskDetailView.SetBorderColor(tcell.ColorDarkOrange) + app.SetFocus(taskDetailView) +} + +func deactivateEditor() { + taskDetailView.Readonly = true + taskDetailView.SetBorderColor(tcell.ColorLightSlateGray) + app.SetFocus(detailPane) +} diff --git a/app/tasks.go b/app/tasks.go new file mode 100644 index 0000000..5dc3f81 --- /dev/null +++ b/app/tasks.go @@ -0,0 +1,84 @@ +package main + +import ( + "fmt" + "time" + + "github.com/gdamore/tcell" + "github.com/rivo/tview" + + "github.com/ajaxray/geek-life/model" +) + +var ( + tasks []model.Task + currentTask *model.Task +) + +func prepareTaskPane() { + taskList = tview.NewList().ShowSecondaryText(false) + taskList.SetDoneFunc(func() { + app.SetFocus(projectPane) + }) + + newTask = makeLightTextInput("+[New Task]"). + SetDoneFunc(func(key tcell.Key) { + switch key { + case tcell.KeyEnter: + task, err := taskRepo.Create(currentProject, newTask.GetText(), "", "", time.Now().Unix()) + if err != nil { + showMessage("[red::]Could not create Task:" + err.Error()) + } + + taskList.AddItem(task.Title, "", 0, nil) + newTask.SetText("") + case tcell.KeyEsc: + app.SetFocus(taskPane) + } + + }) + + taskPane = tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(taskList, 0, 1, true). + AddItem(newTask, 1, 0, false) + + taskPane.SetBorder(true).SetTitle("[::u]T[::-]asks") +} + +func loadTask(idx int) { + contents.RemoveItem(detailPane) + currentTask = &tasks[idx] + + taskName.SetText(fmt.Sprintf("[%s::b]# %s", getTaskTitleColor(*currentTask), currentTask.Title)) + taskDetailView.Buf = makeBufferFromString(currentTask.Details) + taskDetailView.SetColorscheme(colorscheme) + taskDetailView.Start() + + taskDate.SetText("") + if currentTask.DueDate != 0 { + taskDate.SetText(time.Unix(currentTask.DueDate, 0).Format(dateLayoutISO)) + } + + contents.AddItem(detailPane, 0, 3, false) + setStatusToggle(idx) + deactivateEditor() +} + +func getTaskTitleColor(task model.Task) string { + colorName := "whitesmoke" + if task.Completed { + colorName = "lime" + } else if task.DueDate != 0 && task.DueDate < time.Now().Unix() { + colorName = "red" + } + + return colorName +} + +func makeTaskListingTitle(task model.Task) string { + checkbox := "[ []" + if task.Completed { + checkbox = "[x[]" + } + return fmt.Sprintf("[%s]%s %s", getTaskTitleColor(task), checkbox, task.Title) +} diff --git a/app/tui.go b/app/tui.go deleted file mode 100644 index 3775c17..0000000 --- a/app/tui.go +++ /dev/null @@ -1,243 +0,0 @@ -package main - -import ( - "fmt" - "reflect" - "time" - - "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, taskPane *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 - - tasks []model.Task - currentTask model.Task -) - -func main() { - app = tview.NewApplication() - - db = util.ConnectStorm() - defer db.Close() - - projectRepo = repo.NewProjectRepository(db) - taskRepo = repo.NewTaskRepository(db) - - titleText := tview.NewTextView().SetText("[lime::b]Geek-life [::-]- life management for geeks!").SetDynamicColors(true) - cloudStatus := tview.NewTextView().SetText("[::d]Cloud Sync: off").SetTextAlign(tview.AlignRight).SetDynamicColors(true) - - prepareStatusBar() - - titleBar := tview.NewFlex(). - AddItem(titleText, 0, 2, false). - AddItem(cloudStatus, 0, 1, false) - - projectPane = makeProjectPane() - taskPane = makeTaskPane() - - layout := tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(titleBar, 2, 1, false). - AddItem(tview.NewFlex(). - AddItem(projectPane, 25, 1, true). - AddItem(taskPane, 0, 2, false). - AddItem(tview.NewBox().SetBorder(true).SetTitle("Details"), 0, 3, false), - 0, 2, true). - AddItem(statusBar, 1, 1, false) - - setKeyboardShortcuts(projectPane, taskPane) - - if err := app.SetRoot(layout, true).EnableMouse(true).Run(); err != nil { - panic(err) - } -} - -func makeProjectPane() *tview.Flex { - var err error - projects, err = projectRepo.GetAll() - util.FatalIfError(err, "Could not load Projects") - - projectList = tview.NewList() - - for i := range projects { - addProjectToList(i, false) - } - - newProject = makeLightTextInput("+[New Project]"). - SetDoneFunc(func(key tcell.Key) { - switch key { - case tcell.KeyEnter: - project, err := projectRepo.Create(newProject.GetText(), "") - 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())) - projects = append(projects, project) - addProjectToList(len(projects)-1, true) - newProject.SetText("") - } - case tcell.KeyEsc: - app.SetFocus(projectPane) - } - }) - - projectBar := tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(projectList, 0, 1, true). - AddItem(newProject, 1, 0, false) - - projectBar.SetBorder(true).SetTitle("Projects (p)") - - return projectBar -} - -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() { - return func() { loadProject(idx) } - }(i)) - - if selectItem { - projectList.SetCurrentItem(i) - loadProject(i) - } -} - -func makeTaskPane() *tview.Flex { - taskList = tview.NewList().ShowSecondaryText(false) - taskList.SetDoneFunc(func() { - app.SetFocus(projectPane) - }) - - newTask = makeLightTextInput("+[New Task]"). - SetDoneFunc(func(key tcell.Key) { - switch key { - case tcell.KeyEnter: - task, err := taskRepo.Create(currentProject, newTask.GetText(), "", "", time.Now().Unix()) - if err != nil { - showMessage("[red::]Could not create Task:" + err.Error()) - } - - taskList.AddItem(task.Title, "", 0, nil) - newTask.SetText("") - case tcell.KeyEsc: - app.SetFocus(taskPane) - } - - }) - - taskPane := tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(taskList, 0, 1, true). - AddItem(newTask, 1, 0, false) - - taskPane.SetBorder(true).SetTitle("Tasks (t)") - - return taskPane -} - -func loadProject(idx int) { - currentProject = projects[idx] - taskList.Clear() - app.SetFocus(taskPane) - var err error - - if tasks, err = taskRepo.GetAllByProject(currentProject); err != nil && err != storm.ErrNotFound { - showMessage("[red::]Error: " + err.Error()) - } - - for i, task := range tasks { - taskList.AddItem(task.Title, "", 0, func(taskidx int) func() { - return func() { loadTask(taskidx) } - }(i)) - } -} - -func loadTask(idx int) { - currentTask = tasks[idx] - // taskList.Clear() - // app.SetFocus(taskPane) -} - -func makeLightTextInput(placeholder string) *tview.InputField { - return tview.NewInputField(). - SetPlaceholder(placeholder). - SetPlaceholderTextColor(tcell.ColorLightSlateGray). - SetFieldTextColor(tcell.ColorBlack). - SetFieldBackgroundColor(tcell.ColorGray) -} - -func ignoreKeyEvt() bool { - return reflect.TypeOf(app.GetFocus()).String() == "*tview.InputField" -} - -func setKeyboardShortcuts(projectPane *tview.Flex, taskPane *tview.Flex) *tview.Application { - return app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { - if ignoreKeyEvt() { - return event - } - switch event.Rune() { - case 'p': - app.SetFocus(projectPane) - case 't': - app.SetFocus(taskPane) - case 'n': - if projectPane.HasFocus() { - app.SetFocus(newProject) - } else if taskPane.HasFocus() { - app.SetFocus(newTask) - } - } - - return event - }) -} - -func showMessage(text string) { - message.SetText(text) - statusBar.SwitchToPage(messagePage) - - go func() { - app.QueueUpdateDraw(func() { - time.Sleep(time.Second * 5) - statusBar.SwitchToPage(shortcutsPage) - }) - }() -} - -func prepareStatusBar() { - statusBar = tview.NewPages() - - message = tview.NewTextView().SetDynamicColors(true).SetText("Loading...") - statusBar.AddPage(messagePage, message, true, true) - - statusBar.AddPage(shortcutsPage, - 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("Quit: Ctrl+C").SetTextAlign(tview.AlignRight), 0, 3, 1, 1, 0, 0, false), - true, - true, - ) -} diff --git a/app/util.go b/app/util.go new file mode 100644 index 0000000..d4a468a --- /dev/null +++ b/app/util.go @@ -0,0 +1,62 @@ +package main + +import ( + "reflect" + "time" + + "github.com/gdamore/tcell" + "github.com/rivo/tview" + + "github.com/ajaxray/geek-life/util" +) + +func makeHorizontalLine(lineChar rune) *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) + centerY := y + height/2 + for cx := x; cx < x+width; cx++ { + screen.SetContent(cx, centerY, lineChar, nil, style) + } + + // Space for other content. + return x + 1, centerY + 1, width - 2, height - (centerY + 1 - y) + }) + + return hr +} + +func makeLightTextInput(placeholder string) *tview.InputField { + return tview.NewInputField(). + SetPlaceholder(placeholder). + SetPlaceholderTextColor(tcell.ColorLightSlateGray). + SetFieldTextColor(tcell.ColorBlack). + SetFieldBackgroundColor(tcell.ColorGray) +} + +func showMessage(text string) { + message.SetText(text) + statusBar.SwitchToPage(messagePage) + + go func() { + app.QueueUpdateDraw(func() { + time.Sleep(time.Second * 5) + statusBar.SwitchToPage(shortcutsPage) + }) + }() +} + +func makeButton(label string, handler func()) *tview.Button { + btn := tview.NewButton(label).SetSelectedFunc(handler). + SetLabelColor(tcell.ColorWhite) + + btn.SetBackgroundColor(tcell.ColorCornflowerBlue) + + return btn +} + +func ignoreKeyEvt() bool { + textInputs := []string{"*tview.InputField", "*femto.View"} + return util.InArray(reflect.TypeOf(app.GetFocus()).String(), textInputs) +} diff --git a/go.mod b/go.mod index 47fc375..ad23b4b 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ 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/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 diff --git a/go.sum b/go.sum index 1f60b46..ea8d25b 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwv github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM= github.com/asdine/storm/v3 v3.2.0 h1:qFpwwlOyIDVVrAgJliML9fEccRO3PJrJe+KpWK199ho= github.com/asdine/storm/v3 v3.2.0/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0= +github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY= +github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -27,20 +29,28 @@ github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1 github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +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/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= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/tview v0.0.0-20191018125527-685bf6da76c2/go.mod h1:/rBeY22VG2QprWnEqG57IBC8biVu3i0DOIjRLc9I8H0= github.com/rivo/tview v0.0.0-20200507165325-823f280c5426 h1:oUJaa48KPBmtgqppKbVqP4QHRa/cdONfQoXcNWRe7wE= github.com/rivo/tview v0.0.0-20200507165325-823f280c5426/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84= github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/zyedidia/micro v1.4.1 h1:OuszISyaEPK/8xxkklkh7dp2ragvKDEnr4RyHfJcQdo= +github.com/zyedidia/micro v1.4.1/go.mod h1:/wcvhlXPvvvb6v176yUQE4gNzr+Erwz4pWfx7PU/cuE= go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -49,6 +59,7 @@ golang.org/x/net v0.0.0-20191105084925-a882066a44e0 h1:QPlSTtPE2k6PZPasQUbzuK3p9 golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191018095205-727590c5006e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -64,5 +75,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/repository/project.go b/repository/project.go index 07c26c6..17a60bb 100644 --- a/repository/project.go +++ b/repository/project.go @@ -9,5 +9,6 @@ type ProjectRepository interface { GetByUUID(UUID string) (model.Project, error) Create(title, UUID string) (model.Project, error) Update(p *model.Project) error + UpdateField(p *model.Project, field string, value interface{}) error Delete(p *model.Project) error } diff --git a/repository/storm/project.go b/repository/storm/project.go index f8be64e..b763698 100644 --- a/repository/storm/project.go +++ b/repository/storm/project.go @@ -53,6 +53,10 @@ func (repo *projectRepository) Delete(project *model.Project) error { return repo.DB.DeleteStruct(project) } +func (repo *projectRepository) UpdateField(task *model.Project, field string, value interface{}) error { + return repo.DB.UpdateField(task, field, value) +} + func (repo *projectRepository) getOneByField(fieldName string, val interface{}) (model.Project, error) { var project model.Project err := repo.DB.One(fieldName, val, &project) diff --git a/repository/storm/task.go b/repository/storm/task.go index 4603518..f843232 100644 --- a/repository/storm/task.go +++ b/repository/storm/task.go @@ -18,11 +18,11 @@ func NewTaskRepository(db *storm.DB) repository.TaskRepository { return &taskRepository{db} } -func (t taskRepository) GetAll() ([]model.Task, error) { +func (t *taskRepository) GetAll() ([]model.Task, error) { panic("implement me") } -func (t taskRepository) GetAllByProject(project model.Project) ([]model.Task, error) { +func (t *taskRepository) GetAllByProject(project model.Project) ([]model.Task, error) { var tasks []model.Task //err = db.Find("ProjetID", project.ID, &tasks, storm.Limit(10), storm.Skip(10), storm.Reverse()) err := t.DB.Find("ProjectID", project.ID, &tasks) @@ -30,19 +30,19 @@ func (t taskRepository) GetAllByProject(project model.Project) ([]model.Task, er return tasks, err } -func (t taskRepository) GetAllByDate(from, to time.Time) ([]model.Task, error) { +func (t *taskRepository) GetAllByDate(from, to time.Time) ([]model.Task, error) { panic("implement me") } -func (t taskRepository) GetByID(ID string) (model.Task, error) { +func (t *taskRepository) GetByID(ID string) (model.Task, error) { panic("implement me") } -func (t taskRepository) GetByUUID(UUID string) (model.Task, error) { +func (t *taskRepository) GetByUUID(UUID string) (model.Task, error) { panic("implement me") } -func (t taskRepository) Create(project model.Project, title, details, UUID string, dueDate int64) (model.Task, error) { +func (t *taskRepository) Create(project model.Project, title, details, UUID string, dueDate int64) (model.Task, error) { task := model.Task{ ProjectID: project.ID, Title: title, @@ -55,10 +55,14 @@ func (t taskRepository) Create(project model.Project, title, details, UUID strin return task, err } -func (t taskRepository) Update(p *model.Task) error { - panic("implement me") +func (t *taskRepository) Update(task *model.Task) error { + return t.DB.Update(task) } -func (t taskRepository) Delete(p *model.Task) error { +func (t *taskRepository) UpdateField(task *model.Task, field string, value interface{}) error { + return t.DB.UpdateField(task, field, value) +} + +func (t *taskRepository) Delete(task *model.Task) error { panic("implement me") } diff --git a/repository/task.go b/repository/task.go index 152d8fc..2f37a0b 100644 --- a/repository/task.go +++ b/repository/task.go @@ -13,6 +13,7 @@ type TaskRepository interface { GetByID(ID string) (model.Task, error) GetByUUID(UUID string) (model.Task, error) Create(project model.Project, title, details, UUID string, dueDate int64) (model.Task, error) - Update(p *model.Task) error - Delete(p *model.Task) error + Update(t *model.Task) error + UpdateField(t *model.Task, field string, value interface{}) error + Delete(t *model.Task) error }