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:
parent
67e02bf4f5
commit
a0ce5eb412
@ -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))
|
||||||
|
@ -97,6 +97,4 @@ func (widget *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey {
|
|||||||
default:
|
default:
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
return event
|
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,4 @@ func (widget *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey {
|
|||||||
default:
|
default:
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
return event
|
|
||||||
}
|
}
|
||||||
|
@ -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
31
todo/display.go
Normal 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
29
todo/item.go
Normal 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
44
todo/list.go
Normal 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
101
todo/widget.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -204,6 +204,4 @@ func (widget *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey {
|
|||||||
default:
|
default:
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
return event
|
|
||||||
}
|
}
|
||||||
|
3
wtf.go
3
wtf.go
@ -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(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
10
wtf/utils.go
10
wtf/utils.go
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user