Reworks commands and adds deletion of various objects
This commit is contained in:
parent
67eae4866c
commit
a85b782cf0
|
@ -1,3 +1 @@
|
|||
swim
|
||||
*.json
|
||||
*.swim
|
||||
|
|
122
board.go
122
board.go
|
@ -41,6 +41,13 @@ func (b *Board) PollForTermSize() {
|
|||
b.Lanes[b.Current].Stories[b.Lanes[b.Current].Current].offset = 0
|
||||
b.Lanes[b.Current].Stories[b.Lanes[b.Current].Current].Draw(b)
|
||||
} else {
|
||||
for l := range b.Lanes {
|
||||
if len(b.Lanes[l].Stories) == 0 {
|
||||
continue
|
||||
}
|
||||
b.Lanes[l].Current = 0
|
||||
b.Lanes[l].storyOff = 0
|
||||
}
|
||||
b.Draw()
|
||||
}
|
||||
}
|
||||
|
@ -64,9 +71,9 @@ func (b *Board) Run() {
|
|||
case 'Q':
|
||||
Quit()
|
||||
case 'N':
|
||||
b.CreateLane()
|
||||
b.CreateLane("")
|
||||
case 'n':
|
||||
b.CreateStory()
|
||||
b.CreateStory("")
|
||||
case '\n':
|
||||
b.StoryOpen = true
|
||||
b.ViewStory()
|
||||
|
@ -98,26 +105,28 @@ func (b *Board) SetMessage(msg string, isError bool) {
|
|||
b.msgErr = isError
|
||||
}
|
||||
|
||||
func (b *Board) CreateLane() {
|
||||
laneTitle, err := GetAndConfirmCommandLine("Lane Title: ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
return
|
||||
func (b *Board) CreateLane(name string) {
|
||||
var err error
|
||||
if name == "" {
|
||||
name, err = GetAndConfirmCommandLine("Lane Title: ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
return
|
||||
}
|
||||
}
|
||||
b.Lanes = append(b.Lanes, MakeLane(laneTitle))
|
||||
b.Lanes = append(b.Lanes, MakeLane(name))
|
||||
if b.Current < 0 {
|
||||
b.Current = 0
|
||||
}
|
||||
b.SetMessage("Lane created", false)
|
||||
}
|
||||
|
||||
func (b *Board) CreateStory() {
|
||||
func (b *Board) CreateStory(name string) {
|
||||
if b.Current < 0 {
|
||||
b.SetMessage("You must create a lane first", true)
|
||||
return
|
||||
}
|
||||
b.Lanes[b.Current].CreateStory(b)
|
||||
b.SetMessage("Story created", false)
|
||||
b.Lanes[b.Current].CreateStory(name, b)
|
||||
}
|
||||
|
||||
func (b Board) PrintHeader() string {
|
||||
|
@ -201,6 +210,45 @@ func (b *Board) EnterCommand() {
|
|||
}
|
||||
case "q", "quit":
|
||||
Quit()
|
||||
case "c", "create":
|
||||
if len(f) >= 2 {
|
||||
target := strings.ToLower(f[1])
|
||||
name := ""
|
||||
if len(f) > 2 {
|
||||
name = strings.Join(f[2:], " ")
|
||||
}
|
||||
switch target {
|
||||
case "lane", "l", "la", "lan":
|
||||
b.CreateLane(name)
|
||||
case "story", "s", "st", "sto", "stor":
|
||||
b.CreateStory(name)
|
||||
case "task", "tas", "ta", "t":
|
||||
b.Lanes[b.Current].Stories[b.Lanes[b.Current].Current].AddTask(name, b)
|
||||
case "comment", "com", "c":
|
||||
b.Lanes[b.Current].Stories[b.Lanes[b.Current].Current].AddComment(name, b)
|
||||
}
|
||||
}
|
||||
case "d", "del", "delete":
|
||||
if len(f) > 1 {
|
||||
target := strings.ToLower(f[1])
|
||||
switch target {
|
||||
case "lane", "l", "la", "lan":
|
||||
b.DeleteLane()
|
||||
case "story", "s", "st", "sto", "stor":
|
||||
b.Lanes[b.Current].DeleteStory(b)
|
||||
case "t", "ta", "tas", "task":
|
||||
val := ""
|
||||
if len(f) > 2 {
|
||||
val = f[2]
|
||||
}
|
||||
b.Lanes[b.Current].Stories[b.Lanes[b.Current].Current].DeleteTask(val, b)
|
||||
// TODO
|
||||
default:
|
||||
b.SetMessage(fmt.Sprintf("Unknown target %q", f[1]), true)
|
||||
}
|
||||
} else {
|
||||
b.SetMessage("More info needed: 'update [target] [location]'", true)
|
||||
}
|
||||
case "set", "s":
|
||||
if len(f) > 2 {
|
||||
target := strings.ToLower(f[1])
|
||||
|
@ -215,7 +263,7 @@ func (b *Board) EnterCommand() {
|
|||
b.SetMessage(fmt.Sprintf("Unknown target %q", f[1]), true)
|
||||
}
|
||||
} else {
|
||||
b.SetMessage("More info needed: 'update [target] [location]'", true)
|
||||
b.SetMessage("More info needed: 'set [target] [location] [[value]]'", true)
|
||||
}
|
||||
case "user", "toggle", "t", "u":
|
||||
b.Lanes[b.Current].Stories[b.Lanes[b.Current].Current].Update(f, b)
|
||||
|
@ -228,10 +276,16 @@ func (b *Board) Update(args []string) {
|
|||
location := strings.ToLower(args[0])
|
||||
switch location {
|
||||
case "title", "t", "name", "n":
|
||||
title, err := GetAndConfirmCommandLine("New board title: ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
break
|
||||
var title string
|
||||
var err error
|
||||
if len(args) == 1 {
|
||||
title, err = GetAndConfirmCommandLine("New board title: ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
title = strings.Join(args[1:], " ")
|
||||
}
|
||||
b.Title = title
|
||||
b.SetMessage("Board title updated", false)
|
||||
|
@ -304,7 +358,7 @@ func (b *Board) Move(ch rune) {
|
|||
// move selection down a story
|
||||
if b.Lanes[b.Current].Current < len(b.Lanes[b.Current].Stories)-1 {
|
||||
b.Lanes[b.Current].Current += 1
|
||||
if b.Lanes[b.Current].Current * 2 + 1 > b.height-5 {
|
||||
if b.Lanes[b.Current].Current * 3 + 1 > b.height-5 {
|
||||
b.Lanes[b.Current].storyOff += 1
|
||||
}
|
||||
} else {
|
||||
|
@ -384,6 +438,40 @@ func (b *Board) Move(ch rune) {
|
|||
}
|
||||
}
|
||||
|
||||
func (b *Board) DeleteLane() {
|
||||
if len(b.Lanes) < 1 {
|
||||
b.SetMessage("There are no lanes to delete", true)
|
||||
return
|
||||
}
|
||||
cont, err := GetConfirmation("Are you sure? Type 'yes' to delete: ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
return
|
||||
} else if !cont {
|
||||
b.SetMessage("Deletion canceled", true)
|
||||
return
|
||||
}
|
||||
if len(b.Lanes[b.Current].Stories) > 0 {
|
||||
cont, err = GetConfirmation("Are you really sure? There are stories in the lane... ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
return
|
||||
} else if !cont {
|
||||
b.SetMessage("Deletion canceled", true)
|
||||
return
|
||||
}
|
||||
}
|
||||
if b.Current == len(b.Lanes)-1 {
|
||||
b.Lanes = b.Lanes[:len(b.Lanes)-1]
|
||||
} else {
|
||||
b.Lanes = append(b.Lanes[:b.Current], b.Lanes[b.Current+1:]...)
|
||||
}
|
||||
if b.Current > len(b.Lanes)-1 {
|
||||
b.Current -= 1
|
||||
}
|
||||
b.SetMessage("Lane deleted", false)
|
||||
}
|
||||
|
||||
func (b *Board) ViewStory() {
|
||||
if b.Current > -1 {
|
||||
if b.Lanes[b.Current].Current > -1 {
|
||||
|
|
64
lane.go
64
lane.go
|
@ -13,16 +13,21 @@ type Lane struct {
|
|||
storyOff int // offset for the lane slice
|
||||
}
|
||||
|
||||
func (l *Lane) CreateStory(b *Board) {
|
||||
storyTitle, err := GetAndConfirmCommandLine("Story Title: ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
return
|
||||
func (l *Lane) CreateStory(name string, b *Board) {
|
||||
var err error
|
||||
if name == "" {
|
||||
name, err = GetAndConfirmCommandLine("Story Title: ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
return
|
||||
}
|
||||
}
|
||||
l.Stories = append(l.Stories, MakeStory(storyTitle))
|
||||
|
||||
l.Stories = append(l.Stories, MakeStory(name))
|
||||
if l.Current < 0 {
|
||||
l.Current = 0
|
||||
}
|
||||
b.SetMessage("Story created", false)
|
||||
}
|
||||
|
||||
// Zeroes out the offset and current values
|
||||
|
@ -55,20 +60,30 @@ func (l Lane) Header(width int, selected bool) string {
|
|||
func (l Lane) StringSlice(width int, selected bool) []string {
|
||||
out := make([]string, 0, len(l.Stories) * 3 + 1)
|
||||
for i := l.storyOff; i < len(l.Stories); i++ {
|
||||
// Top spacer
|
||||
out = append(out, fmt.Sprintf("%s%*.*s%s", style.Lane, width, width, " ", styleOff))
|
||||
|
||||
// First card row
|
||||
title := l.Stories[i].Title
|
||||
if len(title) > width-4 {
|
||||
title = title[:width-5] + "…"
|
||||
}
|
||||
leadIn := " "
|
||||
if selected && l.Current == i {
|
||||
leadIn = "\033[1m➜ "
|
||||
}
|
||||
out = append(out, fmt.Sprintf("%s%*.*s%s", style.Lane, width, width, " ", styleOff))
|
||||
title := l.Stories[i].Title
|
||||
pts := strconv.Itoa(l.Stories[i].Points)
|
||||
out = append(out, fmt.Sprintf("%s %s%s%-*.*s%s %s", style.Lane, style.Input, leadIn, width-4, width-4, title, style.Lane, styleOff))
|
||||
|
||||
// Second card row
|
||||
pts := strconv.Itoa(l.Stories[i].Points)
|
||||
if pts == "0" || pts[0] == '-' {
|
||||
pts = ""
|
||||
}
|
||||
if len(title) > width-4 {
|
||||
title = title[:width-5] + "…"
|
||||
users := l.Stories[i].GetUserAbbrString()
|
||||
if len(users) > width-4 {
|
||||
users = users[:width-5] + "…"
|
||||
}
|
||||
out = append(out, fmt.Sprintf("%s %s%s%-*.*s\033[7m%*s\033[27m%s %s", style.Lane, style.Input, leadIn, width-7, width-7, title, 3, pts, style.Lane, styleOff))
|
||||
out = append(out, fmt.Sprintf("%s %s %-*.*s\033[7m%*s\033[27m%s %s", style.Lane, style.Input, width-7, width-7, users, 3, pts, style.Lane, styleOff))
|
||||
}
|
||||
if len(out) > 0 {
|
||||
out = append(out, fmt.Sprintf("%s%*.*s%s", style.Lane, width, width, " ", styleOff))
|
||||
|
@ -76,6 +91,31 @@ func (l Lane) StringSlice(width int, selected bool) []string {
|
|||
return out
|
||||
}
|
||||
|
||||
func (l *Lane) DeleteStory(b *Board) {
|
||||
if len(l.Stories) < 1 {
|
||||
b.SetMessage("There are no stories to delete in this lane", true)
|
||||
return
|
||||
}
|
||||
cont, err := GetConfirmation("Are you sure? Type 'yes' to delete: ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
return
|
||||
}
|
||||
if !cont {
|
||||
b.SetMessage("Deletion canceled", true)
|
||||
return
|
||||
}
|
||||
if l.Current == len(l.Stories)-1 {
|
||||
l.Stories = l.Stories[:len(l.Stories)-1]
|
||||
} else {
|
||||
l.Stories = append(l.Stories[:l.Current], l.Stories[l.Current+1:]...)
|
||||
}
|
||||
if l.Current > len(l.Stories)-1 {
|
||||
l.Current -= 1
|
||||
}
|
||||
b.SetMessage("Story deleted", false)
|
||||
}
|
||||
|
||||
func (l *Lane) Update(args []string, b *Board) {
|
||||
location := strings.ToLower(args[0])
|
||||
switch location {
|
||||
|
|
17
main.go
17
main.go
|
@ -73,13 +73,26 @@ func GetLine(prefix string) (string, error) {
|
|||
}
|
||||
|
||||
func GetCommandLine(prefix string) (string, error) {
|
||||
fmt.Print(upAndLeft) // Move up one and over all
|
||||
fmt.Print(style.Input)
|
||||
fmt.Printf("%s%s\033[2K", upAndLeft, style.Input)
|
||||
line, err := GetLine(prefix)
|
||||
fmt.Print(cursorEnd)
|
||||
return line, err
|
||||
}
|
||||
|
||||
func GetConfirmation(prefix string) (bool, error) {
|
||||
ln, err := GetCommandLine(prefix)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
ln = strings.ToLower(ln)
|
||||
switch ln {
|
||||
case "y", "yes", "yeah", "yup":
|
||||
return true, nil
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetAndConfirmCommandLine(prefix string) (string, error) {
|
||||
var conf rune
|
||||
var err error
|
||||
|
|
95
notes.md
95
notes.md
|
@ -1,27 +1,76 @@
|
|||
# swim
|
||||
Current command list:
|
||||
|
||||
A project planning board for the terminal.
|
||||
[variable-value] [[optional-value]]
|
||||
|
||||
:w,
|
||||
:write,
|
||||
:wq
|
||||
[[path]]
|
||||
|
||||
## Structs
|
||||
:q,
|
||||
:quit
|
||||
|
||||
:c,
|
||||
:create
|
||||
l,
|
||||
lane
|
||||
[[title]]
|
||||
s,
|
||||
story,
|
||||
[[title]]
|
||||
t,
|
||||
task,
|
||||
[[value]]
|
||||
c,
|
||||
com,
|
||||
comment
|
||||
[[value]]
|
||||
|
||||
:s,
|
||||
:set
|
||||
b,
|
||||
board
|
||||
t,
|
||||
title
|
||||
[[value]]
|
||||
l,
|
||||
lane
|
||||
t,
|
||||
title
|
||||
[[value]]
|
||||
s,
|
||||
story
|
||||
title
|
||||
[[value]]
|
||||
d,
|
||||
desc,
|
||||
description
|
||||
[[value]]
|
||||
p,
|
||||
pts,
|
||||
points
|
||||
[[point-value]]
|
||||
u,
|
||||
user
|
||||
[[space separated user list to toggle]]
|
||||
:t,
|
||||
:toggle
|
||||
[[task-id]]
|
||||
|
||||
:u,
|
||||
:user
|
||||
[[space separated user list to toggle]]
|
||||
|
||||
:d,
|
||||
:del,
|
||||
:delete
|
||||
l,
|
||||
lane
|
||||
|
||||
s,
|
||||
story
|
||||
|
||||
t,
|
||||
task
|
||||
[[task-id]]
|
||||
|
||||
1. story
|
||||
- title string
|
||||
- body string
|
||||
- points int
|
||||
- tag int // enum representing a color
|
||||
- users []string
|
||||
- comments []comment
|
||||
- created time.time // the time the story was created
|
||||
2. comment
|
||||
- user string
|
||||
- body string
|
||||
- created time.time
|
||||
3. lane
|
||||
- title string
|
||||
- stories []story
|
||||
4. board
|
||||
- title string
|
||||
- body string
|
||||
- created time.time
|
||||
- lanes []lane
|
||||
|
|
115
story.go
115
story.go
|
@ -44,11 +44,13 @@ func (s *Story) View(b *Board) {
|
|||
case 'Q':
|
||||
Quit()
|
||||
case 'c', 'C':
|
||||
s.AddComment(b)
|
||||
s.AddComment("", b)
|
||||
case 't':
|
||||
s.AddTask(b)
|
||||
s.AddTask("", b)
|
||||
case 'd':
|
||||
s.Update([]string{"description"}, b)
|
||||
case 'D':
|
||||
s.DeleteTask("", b)
|
||||
case 'T':
|
||||
s.Update([]string{"title"}, b)
|
||||
case 'u':
|
||||
|
@ -65,11 +67,14 @@ func (s *Story) View(b *Board) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Story) AddComment(b *Board) {
|
||||
comment, err := GetAndConfirmCommandLine("Comment: ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
return
|
||||
func (s *Story) AddComment(comment string, b *Board) {
|
||||
var err error
|
||||
if comment == "" {
|
||||
comment, err = GetAndConfirmCommandLine("Comment: ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
return
|
||||
}
|
||||
}
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
|
@ -179,24 +184,33 @@ func (s *Story) Scroll(dir rune, b *Board) {
|
|||
|
||||
func (s *Story) Update(args []string, b *Board) {
|
||||
location := strings.ToLower(args[0])
|
||||
var stringVal string
|
||||
var err error
|
||||
if len(args) > 1 {
|
||||
stringVal = strings.Join(args[1:], " ")
|
||||
}
|
||||
switch location {
|
||||
case "title":
|
||||
title, err := GetAndConfirmCommandLine("New story title: ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
return
|
||||
if stringVal == "" {
|
||||
stringVal, err = GetAndConfirmCommandLine("New story title: ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
return
|
||||
}
|
||||
}
|
||||
s.Title = title
|
||||
s.Title = stringVal
|
||||
s.Updated = time.Now()
|
||||
b.SetMessage("Story title updated", false)
|
||||
s.BuildStorySlice(b.width)
|
||||
case "description", "d", "desc", "body", "b":
|
||||
body, err := GetAndConfirmCommandLine("New story description: ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
return
|
||||
if stringVal == "" {
|
||||
stringVal, err = GetAndConfirmCommandLine("New story description: ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
return
|
||||
}
|
||||
}
|
||||
s.Body = body
|
||||
s.Body = stringVal
|
||||
s.Updated = time.Now()
|
||||
b.SetMessage("Story body updated", false)
|
||||
s.BuildStorySlice(b.width)
|
||||
|
@ -241,11 +255,11 @@ func (s *Story) Update(args []string, b *Board) {
|
|||
args = append(args, n)
|
||||
}
|
||||
num, err := strconv.Atoi(args[1])
|
||||
num -= 1
|
||||
if err != nil || num < 0 || num >= len(s.Tasks) {
|
||||
if err != nil || num < 1 || num > len(s.Tasks) {
|
||||
b.SetMessage("Invalid task number", true)
|
||||
return
|
||||
}
|
||||
num -= 1
|
||||
s.Tasks[num].Complete = !s.Tasks[num].Complete
|
||||
s.Updated = time.Now()
|
||||
b.SetMessage("Task state updated", false)
|
||||
|
@ -276,11 +290,14 @@ func (s *Story) AddRemoveUser(users []string, b *Board) {
|
|||
b.SetMessage("Updated user list", false)
|
||||
}
|
||||
|
||||
func (s *Story) AddTask(b *Board) {
|
||||
body, err := GetAndConfirmCommandLine("New task: ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
return
|
||||
func (s *Story) AddTask(body string, b *Board) {
|
||||
var err error
|
||||
if body == "" {
|
||||
body, err = GetAndConfirmCommandLine("New task: ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
return
|
||||
}
|
||||
}
|
||||
s.Tasks = append(s.Tasks, Task{body, false})
|
||||
s.Updated = time.Now()
|
||||
|
@ -288,6 +305,56 @@ func (s *Story) AddTask(b *Board) {
|
|||
b.SetMessage("Task added", false)
|
||||
}
|
||||
|
||||
func (s *Story) DeleteTask(id string, b *Board) {
|
||||
var err error
|
||||
if id == "" {
|
||||
id, err = GetCommandLine("Task # to delete: ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
return
|
||||
}
|
||||
}
|
||||
num, err := strconv.Atoi(id)
|
||||
if err != nil || num < 1 || num > len(s.Tasks) {
|
||||
b.SetMessage("Invalid task number", true)
|
||||
return
|
||||
}
|
||||
num -= 1
|
||||
var cont bool
|
||||
cont, err = GetConfirmation("Are you sure? Type 'yes' to delete: ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
return
|
||||
} else if !cont {
|
||||
b.SetMessage("Deletion canceled", true)
|
||||
return
|
||||
}
|
||||
if num == len(s.Tasks)-1 {
|
||||
s.Tasks = s.Tasks[:len(s.Tasks)-1]
|
||||
} else {
|
||||
s.Tasks = append(s.Tasks[:num], s.Tasks[num+1:]...)
|
||||
}
|
||||
s.Updated = time.Now()
|
||||
s.BuildStorySlice(b.width)
|
||||
b.SetMessage("Task deleted", false)
|
||||
}
|
||||
|
||||
|
||||
func (s Story) GetUserAbbrString() string {
|
||||
var out strings.Builder
|
||||
for i := range s.Users {
|
||||
if len(s.Users[i]) > 1 {
|
||||
out.WriteString(strings.ToUpper(s.Users[i][:2]))
|
||||
} else {
|
||||
out.WriteString(strings.ToUpper(string(s.Users[i][0])))
|
||||
}
|
||||
if i < len(s.Users)-1 {
|
||||
out.WriteString(", ")
|
||||
}
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func (s Story) Duplicate() Story {
|
||||
out := Story{}
|
||||
out.Title = s.Title
|
||||
|
|
Loading…
Reference in New Issue