1
0
mirror of https://github.com/taigrr/wtf synced 2025-01-18 04:03:14 -08:00

Basic selectable todo functionality working

Can:
- move between todo items
- toggle checked/unchecked state

Cannot:
- persiste changes to file
- add items
- delete items
This commit is contained in:
Chris Cummer 2018-04-20 15:19:54 -07:00
parent 67e02bf4f5
commit a0ce5eb412
12 changed files with 279 additions and 28 deletions

View File

@ -64,8 +64,6 @@ func (widget *Widget) formatChange(line string) string {
line = strings.Replace(line, "M", "[yellow]M[white]", 1) line = strings.Replace(line, "M", "[yellow]M[white]", 1)
case 'R': case 'R':
line = strings.Replace(line, "R", "[purple]R[white]", 1) line = strings.Replace(line, "R", "[purple]R[white]", 1)
default:
line = line
} }
return fmt.Sprintf(" %s\n", strings.Replace(line, "\"", "", -1)) return fmt.Sprintf(" %s\n", strings.Replace(line, "\"", "", -1))

View File

@ -97,6 +97,4 @@ func (widget *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey {
default: default:
return event return event
} }
return event
} }

View File

@ -96,6 +96,4 @@ func (widget *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey {
default: default:
return event return event
} }
return event
} }

View File

@ -2,7 +2,6 @@ package textfile
import ( import (
"fmt" "fmt"
"io/ioutil"
"time" "time"
"github.com/olebedev/config" "github.com/olebedev/config"
@ -20,8 +19,8 @@ type Widget struct {
func NewWidget() *Widget { func NewWidget() *Widget {
widget := Widget{ widget := Widget{
TextWidget: wtf.NewTextWidget(" Text File ", "textfile"), TextWidget: wtf.NewTextWidget(" 📄 Text File ", "textfile"),
FilePath: Config.UString("wtf.mods.textfile.filepath"), FilePath: Config.UString("wtf.mods.textfile.filename"),
} }
widget.View.SetWrap(true) widget.View.SetWrap(true)
@ -41,7 +40,7 @@ func (widget *Widget) Refresh() {
widget.View.Clear() widget.View.Clear()
fileData, err := widget.readFile() fileData, err := wtf.ReadFile(widget.FilePath)
if err != nil { if err != nil {
fmt.Fprintf(widget.View, "%s", err) fmt.Fprintf(widget.View, "%s", err)
@ -49,16 +48,3 @@ func (widget *Widget) Refresh() {
fmt.Fprintf(widget.View, "%s", fileData) fmt.Fprintf(widget.View, "%s", fileData)
} }
} }
/* -------------------- Uneported Functions -------------------- */
func (widget *Widget) readFile() (string, error) {
absPath, _ := wtf.ExpandHomeDir(widget.FilePath)
bytes, err := ioutil.ReadFile(absPath)
if err != nil {
return "", err
}
return string(bytes), nil
}

31
todo/display.go Normal file
View File

@ -0,0 +1,31 @@
package todo
import (
"fmt"
)
func (widget *Widget) display() {
widget.View.Clear()
title := fmt.Sprintf(" 📝 %s ", widget.FilePath)
widget.View.SetTitle(title)
str := ""
for idx, item := range widget.list.Items {
foreColor, backColor := "white", "black"
if widget.View.HasFocus() && idx == widget.list.selected {
foreColor, backColor = "black", "olive"
}
str = str + fmt.Sprintf(
"[%s:%s]|%s| %s[white]\n",
foreColor,
backColor,
item.CheckMark(),
item.Text,
)
}
fmt.Fprintf(widget.View, "%s", str)
}

29
todo/item.go Normal file
View File

@ -0,0 +1,29 @@
package todo
import(
"time"
)
type Item struct {
Checked bool
Index int
Text string
createdAt time.Time
updatedAt time.Time
}
func (item *Item) CheckMark() string {
if item.Checked {
return "x"
} else {
return " "
}
}
func (item *Item) Toggle() {
item.Checked = !item.Checked
item.updatedAt = time.Now()
}

44
todo/list.go Normal file
View File

@ -0,0 +1,44 @@
package todo
import ()
type List struct {
Items []*Item
selected int
}
func (list *List) Len() int {
return len(list.Items)
}
func (list *List) Less(i, j int) bool {
return list.Items[i].Index < list.Items[j].Index
}
func (list *List) Swap(i, j int) {
list.Items[i], list.Items[j] = list.Items[j], list.Items[i]
}
func (list *List) Next() {
list.selected = list.selected + 1
if list.selected >= len(list.Items) {
list.selected = 0
}
}
func (list *List) Prev() {
list.selected = list.selected - 1
if list.selected < 0 {
list.selected = len(list.Items) - 1
}
}
// Toggle switches the checked state of the selected item
func (list *List) Toggle() {
list.Items[list.selected].Toggle()
}
func (list *List) Unselect() {
list.selected = -1
}

101
todo/widget.go Normal file
View File

@ -0,0 +1,101 @@
package todo
import (
"fmt"
"time"
"github.com/gdamore/tcell"
"github.com/olebedev/config"
"github.com/senorprogrammer/wtf/wtf"
"gopkg.in/yaml.v2"
)
// Config is a pointer to the global config object
var Config *config.Config
type Widget struct {
wtf.TextWidget
FilePath string
list *List
}
func NewWidget() *Widget {
widget := Widget{
TextWidget: wtf.NewTextWidget(" 📝 Todo ", "todo"),
FilePath: Config.UString("wtf.mods.todo.filename"),
list: &List{selected: -1},
}
widget.init()
widget.View.SetInputCapture(widget.keyboardIntercept)
return &widget
}
/* -------------------- Exported Functions -------------------- */
func (widget *Widget) Refresh() {
if widget.Disabled() {
return
}
confDir, _ := wtf.ConfigDir()
fileData, _ := wtf.ReadYamlFile(fmt.Sprintf("%s/%s", confDir, widget.FilePath))
yaml.Unmarshal(fileData, &widget.list)
widget.display()
widget.RefreshedAt = time.Now()
}
/* -------------------- Unexported Functions -------------------- */
func (widget *Widget) init() {
_, err := wtf.CreateFile(widget.FilePath)
if err != nil {
panic(err)
}
}
func (widget *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey {
switch string(event.Rune()) {
case " ":
// Check/uncheck selected item
widget.list.Toggle()
widget.display()
return nil
case "e":
// Edit selected item
return nil
case "n":
// Add a new item
return nil
}
switch event.Key() {
case tcell.KeyCtrlD:
// Delete selected item
return nil
case tcell.KeyDown:
widget.list.Next()
widget.display()
return nil
//case tcell.KeySpac:
//// Check/uncheck an item
//return nil
case tcell.KeyEsc:
// Unselect the current row and pass the key on through to unselect the widget
widget.list.Unselect()
widget.display()
return event
case tcell.KeyUp:
// Select next item up
widget.list.Prev()
widget.display()
return nil
default:
return event
}
}

View File

@ -204,6 +204,4 @@ func (widget *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey {
default: default:
return event return event
} }
return event
} }

3
wtf.go
View File

@ -20,6 +20,7 @@ import (
"github.com/senorprogrammer/wtf/security" "github.com/senorprogrammer/wtf/security"
"github.com/senorprogrammer/wtf/status" "github.com/senorprogrammer/wtf/status"
"github.com/senorprogrammer/wtf/textfile" "github.com/senorprogrammer/wtf/textfile"
"github.com/senorprogrammer/wtf/todo"
"github.com/senorprogrammer/wtf/weather" "github.com/senorprogrammer/wtf/weather"
"github.com/senorprogrammer/wtf/wtf" "github.com/senorprogrammer/wtf/wtf"
) )
@ -126,6 +127,7 @@ func main() {
security.Config = Config security.Config = Config
status.Config = Config status.Config = Config
textfile.Config = Config textfile.Config = Config
todo.Config = Config
weather.Config = Config weather.Config = Config
Widgets = []wtf.TextViewer{ Widgets = []wtf.TextViewer{
@ -140,6 +142,7 @@ func main() {
security.NewWidget(), security.NewWidget(),
status.NewWidget(), status.NewWidget(),
textfile.NewWidget(), textfile.NewWidget(),
todo.NewWidget(),
weather.NewWidget(), weather.NewWidget(),
} }

View File

@ -3,16 +3,26 @@ package wtf
import ( import (
"fmt" "fmt"
"os" "os"
"io/ioutil"
"github.com/olebedev/config" "github.com/olebedev/config"
) )
func ConfigDir() (string, error) {
configDir, err := ExpandHomeDir("~/.wtf/")
if err != nil {
return "", err
}
return configDir, nil
}
// CreateConfigDir creates the .wtf directory in the user's home dir // CreateConfigDir creates the .wtf directory in the user's home dir
func CreateConfigDir() bool { func CreateConfigDir() bool {
homeDir, _ := ExpandHomeDir("~/.wtf/") configDir, _ := ConfigDir()
if _, err := os.Stat(homeDir); os.IsNotExist(err) { if _, err := os.Stat(configDir); os.IsNotExist(err) {
err := os.Mkdir(homeDir, os.ModePerm) err := os.Mkdir(configDir, os.ModePerm)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -21,6 +31,34 @@ func CreateConfigDir() bool {
return true return true
} }
// CreateFile creates the named file in the config directory, if it does not already exist.
// If the file exists it does not recreate it.
// If successful, eturns the absolute path to the file
// If unsuccessful, returns an error
func CreateFile(fileName string) (string, error) {
configDir, err := ConfigDir()
if err != nil {
return "", err
}
filePath := fmt.Sprintf("%s/%s", configDir, fileName)
// Check if the file already exists; if it does not, create it
_, err = os.Stat(filePath)
if err != nil {
if os.IsNotExist(err) {
_, err = os.Create(filePath)
if err != nil {
return "", err
}
} else {
return "", err
}
}
return filePath, nil
}
// LoadConfigFile loads the config.yml file to configure the app // LoadConfigFile loads the config.yml file to configure the app
func LoadConfigFile(filePath string) *config.Config { func LoadConfigFile(filePath string) *config.Config {
absPath, _ := ExpandHomeDir(filePath) absPath, _ := ExpandHomeDir(filePath)
@ -34,3 +72,20 @@ func LoadConfigFile(filePath string) *config.Config {
return cfg return cfg
} }
func ReadFile(fileName string) (string, error) {
configDir, err := ConfigDir()
if err != nil {
return "", err
}
filePath := fmt.Sprintf("%s/%s", configDir, fileName)
bytes, err := ioutil.ReadFile(filePath)
if err != nil {
return "", err
}
return string(bytes), nil
}

View File

@ -82,6 +82,16 @@ func Now() time.Time {
return time.Now().Local() return time.Now().Local()
} }
func ReadYamlFile(filePath string) ([]byte, error) {
file, err := ioutil.ReadFile(filePath)
if err != nil {
return []byte{}, err
}
return file, nil
}
func RightAlignFormat(view *tview.TextView) string { func RightAlignFormat(view *tview.TextView) string {
_, _, w, _ := view.GetInnerRect() _, _, w, _ := view.GetInnerRect()
return fmt.Sprintf("%%%ds", w-1) return fmt.Sprintf("%%%ds", w-1)