Viewing stories now works
This commit is contained in:
parent
a3bc3814e0
commit
401526bccc
44
board.go
44
board.go
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -31,6 +30,7 @@ type Board struct {
|
|||
Zoom int
|
||||
}
|
||||
|
||||
|
||||
func (b *Board) Run() {
|
||||
defer termios.Restore()
|
||||
termios.SetCharMode()
|
||||
|
@ -42,20 +42,16 @@ func (b *Board) Run() {
|
|||
|
||||
switch ch {
|
||||
case 'Q':
|
||||
termios.Restore()
|
||||
fmt.Print("\033[?25h")
|
||||
os.Exit(0)
|
||||
Quit()
|
||||
case 'N':
|
||||
b.CreateLane()
|
||||
case 'n':
|
||||
b.CreateStory()
|
||||
case '\n':
|
||||
// View current story
|
||||
b.ViewStory()
|
||||
case 'h', 'j', 'k', 'l', 'H', 'L', 'K', 'J':
|
||||
b.ClearMessage()
|
||||
b.Move(ch)
|
||||
case 'c':
|
||||
// Comment on current story
|
||||
case 'D':
|
||||
// Delete current story
|
||||
case 'e':
|
||||
|
@ -66,6 +62,8 @@ func (b *Board) Run() {
|
|||
b.ZoomIn()
|
||||
case '-':
|
||||
b.ZoomOut()
|
||||
default:
|
||||
b.SetMessage(fmt.Sprintf("There is no action bount to '%c'", ch), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -94,6 +92,10 @@ func (b *Board) CreateLane() {
|
|||
}
|
||||
|
||||
func (b *Board) CreateStory() {
|
||||
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)
|
||||
}
|
||||
|
@ -115,7 +117,7 @@ func (b Board) GetLaneSlices(width int) [][]string {
|
|||
} else {
|
||||
s = make([]string, 0)
|
||||
}
|
||||
laneText[i] = s
|
||||
laneText[i-b.LaneOff] = s
|
||||
}
|
||||
return laneText
|
||||
}
|
||||
|
@ -198,7 +200,6 @@ func (b *Board) ZoomOut() {
|
|||
|
||||
func (b *Board) Move(ch rune) {
|
||||
if len(b.Lanes) == 0 {
|
||||
b.SetMessage("You cannot move what does not exist", true)
|
||||
return
|
||||
}
|
||||
switch ch {
|
||||
|
@ -206,6 +207,9 @@ func (b *Board) Move(ch rune) {
|
|||
// move left a lane
|
||||
if b.Current > 0 {
|
||||
b.Current -= 1
|
||||
if b.Current < b.LaneOff {
|
||||
b.LaneOff -= 1
|
||||
}
|
||||
} else {
|
||||
b.SetMessage("Cannot move further left", true)
|
||||
}
|
||||
|
@ -213,6 +217,9 @@ func (b *Board) Move(ch rune) {
|
|||
// move selection right a lane
|
||||
if b.Current < len(b.Lanes)-1 {
|
||||
b.Current += 1
|
||||
if b.Current > b.LaneOff + b.Zoom - 1 {
|
||||
b.LaneOff += 1
|
||||
}
|
||||
} else {
|
||||
b.SetMessage("Cannot move further right", true)
|
||||
}
|
||||
|
@ -220,6 +227,9 @@ 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 {
|
||||
b.Lanes[b.Current].StoryOff += 1
|
||||
}
|
||||
} else {
|
||||
b.SetMessage("Cannot move further down", true)
|
||||
}
|
||||
|
@ -227,6 +237,9 @@ func (b *Board) Move(ch rune) {
|
|||
// move selection up a story
|
||||
if b.Lanes[b.Current].Current > 0 {
|
||||
b.Lanes[b.Current].Current -= 1
|
||||
if b.Lanes[b.Current].Current < b.Lanes[b.Current].StoryOff {
|
||||
b.Lanes[b.Current].StoryOff -= 1
|
||||
}
|
||||
} else {
|
||||
b.SetMessage("Cannot move further up", true)
|
||||
}
|
||||
|
@ -271,6 +284,7 @@ func (b *Board) Move(ch rune) {
|
|||
b.Lanes[b.Current+1].Current = len(b.Lanes[b.Current+1].Stories)-1
|
||||
MoveRight: b.Move('l')
|
||||
case 'K':
|
||||
// moves story up
|
||||
storyIndex := b.Lanes[b.Current].Current
|
||||
if storyIndex <= 0 {
|
||||
b.SetMessage("Cannot move story up", true)
|
||||
|
@ -280,6 +294,7 @@ func (b *Board) Move(ch rune) {
|
|||
swapper(storyIndex, storyIndex-1)
|
||||
b.Move('k')
|
||||
case 'J':
|
||||
// moves story down
|
||||
storyIndex := b.Lanes[b.Current].Current
|
||||
if storyIndex > len(b.Lanes[b.Current].Stories)-2 {
|
||||
b.SetMessage("Cannot move story down", true)
|
||||
|
@ -292,6 +307,17 @@ func (b *Board) Move(ch rune) {
|
|||
}
|
||||
}
|
||||
|
||||
func (b *Board) ViewStory() {
|
||||
if b.Current > -1 {
|
||||
if b.Lanes[b.Current].Current > -1 {
|
||||
b.Lanes[b.Current].Stories[b.Lanes[b.Current].Current].View(b)
|
||||
}
|
||||
} else {
|
||||
b.SetMessage("There is no story to view", true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (b Board) Draw() {
|
||||
var out strings.Builder
|
||||
out.WriteString(cursorHome)
|
||||
|
|
14
lane.go
14
lane.go
|
@ -5,9 +5,10 @@ import (
|
|||
)
|
||||
|
||||
type Lane struct {
|
||||
Title string
|
||||
Stories []Story
|
||||
Current int // Index of current story
|
||||
Title string
|
||||
Stories []Story
|
||||
Current int // Index of current story
|
||||
StoryOff int // offset for the lane slice
|
||||
}
|
||||
|
||||
func (l *Lane) CreateStory(b *Board) {
|
||||
|
@ -39,16 +40,15 @@ 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, story := range l.Stories {
|
||||
for i := l.StoryOff; i < len(l.Stories); i++ {
|
||||
leadIn := " "
|
||||
if selected && l.Current == i {
|
||||
leadIn = "\033[1m➜\033[21m "
|
||||
}
|
||||
out = append(out, fmt.Sprintf("%s%*.*s%s", style.Lane, width, width, " ", styleOff))
|
||||
out = append(out, fmt.Sprintf("%s %s%s%-*.*s%s %s", style.Lane, style.Input, leadIn, width-4, width-4, story.Title, style.Lane, styleOff))
|
||||
out = append(out, fmt.Sprintf("%s %s%s%-*.*s%s %s", style.Lane, style.Input, leadIn, width-4, width-4, l.Stories[i].Title, style.Lane, styleOff))
|
||||
}
|
||||
if len(out) > 0 {
|
||||
out = append(out, fmt.Sprintf("%s%*.*s%s", style.Lane, width, width, " ", styleOff))
|
||||
|
@ -57,5 +57,5 @@ func (l Lane) StringSlice(width int, selected bool) []string {
|
|||
}
|
||||
|
||||
func MakeLane(title string) Lane {
|
||||
return Lane{title, make([]Story,0,5), -1}
|
||||
return Lane{title, make([]Story,0,5), -1, 0}
|
||||
}
|
||||
|
|
34
main.go
34
main.go
|
@ -4,6 +4,7 @@ import (
|
|||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"tildegit.org/sloum/swim/termios"
|
||||
"time"
|
||||
)
|
||||
|
@ -56,7 +57,7 @@ func GetAndConfirmCommandLine(prefix string) (string, error) {
|
|||
fmt.Print(style.Input)
|
||||
fmt.Printf("%s%s%sIs %q correct? (y/n/c)", cursorEnd, upAndLeft, style.Input, line)
|
||||
conf = Getch()
|
||||
if conf == 'y' {
|
||||
if conf == 'y' || conf == '\n' {
|
||||
break
|
||||
} else if conf == 'n' {
|
||||
fmt.Print(cursorEnd)
|
||||
|
@ -72,6 +73,37 @@ func GetAndConfirmCommandLine(prefix string) (string, error) {
|
|||
return line, err
|
||||
}
|
||||
|
||||
// Adapted From:
|
||||
// https://gist.github.com/kennwhite/306317d81ab4a885a965e25aa835b8ef
|
||||
func WrapText(text string, lineWidth int) string {
|
||||
var wrapped strings.Builder
|
||||
words := strings.Fields(strings.TrimSpace(text))
|
||||
if len(words) == 0 {
|
||||
return text
|
||||
}
|
||||
wrapped.WriteString(words[0])
|
||||
spaceLeft := lineWidth - wrapped.Len()
|
||||
for _, word := range words[1:] {
|
||||
if len(word)+1 > spaceLeft {
|
||||
wrapped.WriteRune('\n')
|
||||
wrapped.WriteString(word)
|
||||
spaceLeft = lineWidth - len(word)
|
||||
} else {
|
||||
wrapped.WriteRune(' ')
|
||||
wrapped.WriteString(word)
|
||||
spaceLeft -= 1 + len(word)
|
||||
}
|
||||
}
|
||||
|
||||
return wrapped.String()
|
||||
}
|
||||
|
||||
func Quit() {
|
||||
termios.Restore()
|
||||
fmt.Print("\033[?25h")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func main() {
|
||||
style.Init(SimpleColor)
|
||||
cols, rows := termios.GetWindowSize()
|
||||
|
|
131
story.go
131
story.go
|
@ -1,6 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/user"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -13,6 +16,132 @@ type Story struct {
|
|||
Comments []Comment
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
Offset int
|
||||
StSlice []string
|
||||
Points int
|
||||
}
|
||||
|
||||
func (s *Story) View(b *Board) {
|
||||
s.BuildStorySlice(b.Width)
|
||||
var ch rune
|
||||
for {
|
||||
s.Draw(b)
|
||||
ch = Getch()
|
||||
|
||||
switch ch {
|
||||
case 'j', 'k':
|
||||
s.Scroll(ch, b)
|
||||
case ':':
|
||||
b.EnterCommand()
|
||||
case 'x':
|
||||
s.Offset = 0
|
||||
return
|
||||
case 'Q':
|
||||
Quit()
|
||||
case 'c', 'C':
|
||||
s.AddComment(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Story) AddComment(b *Board) {
|
||||
comment, err := GetAndConfirmCommandLine("Comment: ")
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
return
|
||||
}
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
b.SetMessage(err.Error(), true)
|
||||
return
|
||||
}
|
||||
s.Comments = append(s.Comments, Comment{u.Name, comment, time.Now()})
|
||||
s.BuildStorySlice(b.Width)
|
||||
}
|
||||
|
||||
func (s *Story) BuildStorySlice(width int) {
|
||||
var out strings.Builder
|
||||
out.WriteRune('\n')
|
||||
out.WriteString(WrapText(s.Title, width-7))
|
||||
out.WriteRune('\n')
|
||||
out.WriteRune('\n')
|
||||
out.WriteString("Updated: ")
|
||||
out.WriteString(s.Updated.Format(time.UnixDate))
|
||||
pts := s.Points
|
||||
if pts < 1 {
|
||||
out.WriteString("\nPoints: -\n\n")
|
||||
} else {
|
||||
out.WriteString(fmt.Sprintf("\nPoints: %d\n\n", s.Points))
|
||||
}
|
||||
out.WriteString("Users: ")
|
||||
out.WriteString(WrapText(strings.Join(s.Users, ", "), width-7))
|
||||
out.WriteRune('\n')
|
||||
out.WriteRune('\n')
|
||||
out.WriteString("Description:\n\n")
|
||||
out.WriteString(WrapText(s.Body, width))
|
||||
out.WriteRune('\n')
|
||||
out.WriteRune('\n')
|
||||
for i, task := range s.Tasks {
|
||||
out.WriteString(fmt.Sprintf("%2d. ", i+1))
|
||||
if task.Complete {
|
||||
out.WriteString("☒ ")
|
||||
} else {
|
||||
out.WriteString("☐ ")
|
||||
}
|
||||
out.WriteString(WrapText(task.Body, width-6))
|
||||
out.WriteRune('\n')
|
||||
}
|
||||
out.WriteRune('\n')
|
||||
out.WriteString(strings.Repeat("━", width))
|
||||
out.WriteString("\n\nComments:\n\n")
|
||||
for _, c := range s.Comments {
|
||||
out.WriteString(c.User)
|
||||
out.WriteRune('\n')
|
||||
out.WriteString(WrapText(c.Created.Format(time.UnixDate), width))
|
||||
out.WriteRune('\n')
|
||||
out.WriteRune('\n')
|
||||
out.WriteString(WrapText(c.Body, width))
|
||||
out.WriteRune('\n')
|
||||
out.WriteRune('\n')
|
||||
out.WriteString(strings.Repeat("╍", width))
|
||||
out.WriteRune('\n')
|
||||
out.WriteRune('\n')
|
||||
}
|
||||
s.StSlice = strings.Split(out.String(), "\n")
|
||||
}
|
||||
|
||||
func (s Story) Draw(b *Board) {
|
||||
var out strings.Builder
|
||||
out.WriteString(cursorHome)
|
||||
out.WriteString(b.PrintHeader())
|
||||
for i := 0; i < b.Height-3; i++ {
|
||||
index := i+s.Offset
|
||||
if index >= len(s.StSlice) {
|
||||
out.WriteString(fmt.Sprintf("%*.*s\n", b.Width, b.Width, ""))
|
||||
} else {
|
||||
out.WriteString(fmt.Sprintf("%-*.*s\n", b.Width, b.Width, s.StSlice[index]))
|
||||
}
|
||||
}
|
||||
out.WriteString(b.PrintInputArea())
|
||||
out.WriteString(b.PrintMessage())
|
||||
fmt.Print(out.String())
|
||||
}
|
||||
|
||||
func (s *Story) Scroll(dir rune, b *Board) {
|
||||
switch dir {
|
||||
case 'j':
|
||||
if s.Offset + b.Height - 3 < len(s.StSlice) {
|
||||
s.Offset += 1
|
||||
} else {
|
||||
b.SetMessage("Cannot move further down", true)
|
||||
}
|
||||
case 'k':
|
||||
if s.Offset > 0 {
|
||||
s.Offset -= 1
|
||||
} else {
|
||||
b.SetMessage("Cannot move further up", true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s Story) Duplicate() Story {
|
||||
|
@ -32,5 +161,5 @@ func (s Story) Duplicate() Story {
|
|||
}
|
||||
|
||||
func MakeStory(title string) Story {
|
||||
return Story{title,"", make([]string,0,2), -1, make([]Task,0,2), make([]Comment,0,2), time.Now(), time.Now()}
|
||||
return Story{title,"", make([]string,0,2), -1, make([]Task,0,2), make([]Comment,0,2), time.Now(), time.Now(), 0, []string{}, -1}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue