2
1
Fork 0

Fully movable stories with error messaging for movement

This commit is contained in:
sloum 2021-03-23 23:15:29 -07:00
parent c972b2e2f5
commit a3bc3814e0
7 changed files with 227 additions and 53 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
swim

197
board.go
View File

@ -3,6 +3,7 @@ package main
import (
"fmt"
"os"
"reflect"
"strings"
"time"
"tildegit.org/sloum/swim/termios"
@ -22,6 +23,7 @@ type Board struct {
Created time.Time
Lanes []Lane
Current int // Index of current lane
LaneOff int
Message string
MsgErr bool
Width int
@ -41,6 +43,7 @@ func (b *Board) Run() {
switch ch {
case 'Q':
termios.Restore()
fmt.Print("\033[?25h")
os.Exit(0)
case 'N':
b.CreateLane()
@ -48,27 +51,21 @@ func (b *Board) Run() {
b.CreateStory()
case '\n':
// View current story
case 'h', 'j', 'k', 'l':
// Move cursor, context dependent
// If a story is open, will scroll
// the story, otherwise will select
// a story
case 'h', 'j', 'k', 'l', 'H', 'L', 'K', 'J':
b.ClearMessage()
b.Move(ch)
case 'c':
// Comment on current story
case 'a':
// Archive the current story
case 'd':
// Delete current story
case 'D':
// Delete current lane
// Delete current story
case 'e':
// Edit current story
case ':':
b.EnterCommand()
case '+':
// b.ZoomIn()
b.ZoomIn()
case '-':
// b.ZoomOut()
b.ZoomOut()
}
}
}
@ -93,10 +90,12 @@ func (b *Board) CreateLane() {
if b.Current < 0 {
b.Current = 0
}
b.SetMessage("Lane created", false)
}
func (b *Board) CreateStory() {
b.Lanes[b.Current].CreateStory(b)
b.SetMessage("Story created", false)
}
func (b Board) PrintHeader() string {
@ -107,38 +106,56 @@ func (b Board) PrintInputArea() string {
return fmt.Sprintf("%s%-*.*s%s\n", style.Input, b.Width, b.Width, " ", styleOff)
}
func (b Board) GetLaneSlices(width int) [][]string {
laneText := make([][]string, b.Zoom)
for i := b.LaneOff; i < b.LaneOff + b.Zoom; i++ {
var s []string
if i < len(b.Lanes) {
s = b.Lanes[i].StringSlice(width, i == b.Current)
} else {
s = make([]string, 0)
}
laneText[i] = s
}
return laneText
}
func (b Board) LaneHeaderRow(width, pad int) string {
var out strings.Builder
for i := b.LaneOff; i < b.LaneOff + b.Zoom; i++ {
if i < len(b.Lanes) {
out.WriteString(b.Lanes[i].Header(width, i == b.Current))
} else {
out.WriteString(fmt.Sprintf("%s%*.*s%s", style.Lane, width, width, " ", styleOff))
}
}
if pad > 0 {
out.WriteString(fmt.Sprintf("%s%*.*s%s", style.Lane, pad, pad, " ", styleOff))
}
out.WriteRune('\n')
return out.String()
}
func (b Board) PrintLanes() string {
var out strings.Builder
laneWidth := b.Width / b.Zoom
laneSlices := b.GetLaneSlices(laneWidth)
pad := b.Width - laneWidth * b.Zoom
if len(b.Lanes) == 0 {
for i := 0; i < b.Height - 3; i++ {
out.WriteString(fmt.Sprintf("%s%*.*s%s\n", style.Lane, b.Width, b.Width, " ", styleOff))
}
return out.String()
}
laneText := make([][]string, 0, len(b.Lanes))
maxLen := 0
for _, l := range b.Lanes {
s := l.StringSlice(laneWidth)
laneText = append(laneText, s)
if len(s) > maxLen {
maxLen = len(s)
}
}
for i := 0; i < b.Height - 3; i++ {
for li, l := range laneText {
// TODO fix this
if li >= b.Zoom {
out.WriteRune('\n')
break
}
if i < len(l) {
out.WriteString(l[i])
out.WriteString(b.LaneHeaderRow(laneWidth, pad))
for row := 0; row < b.Height - 4; row++ {
for _, l := range laneSlices {
if row < len(l) {
out.WriteString(l[row])
} else {
out.WriteString(fmt.Sprintf("%s%*.*s%s", style.Lane, laneWidth, laneWidth, " ", styleOff))
}
}
if pad > 0 {
out.WriteString(fmt.Sprintf("%s%*.*s%s", style.Lane, pad, pad, " ", styleOff))
}
out.WriteRune('\n')
}
out.WriteString(styleOff)
return out.String()
@ -167,6 +184,114 @@ func (b Board) PrintMessage() string {
return out.String()
}
func (b *Board) ZoomIn() {
if b.Zoom > 1 {
b.Zoom -= 1
}
}
func (b *Board) ZoomOut() {
if b.Width / (b.Zoom+1) > 15 {
b.Zoom += 1
}
}
func (b *Board) Move(ch rune) {
if len(b.Lanes) == 0 {
b.SetMessage("You cannot move what does not exist", true)
return
}
switch ch {
case 'h':
// move left a lane
if b.Current > 0 {
b.Current -= 1
} else {
b.SetMessage("Cannot move further left", true)
}
case 'l':
// move selection right a lane
if b.Current < len(b.Lanes)-1 {
b.Current += 1
} else {
b.SetMessage("Cannot move further right", true)
}
case 'j':
// move selection down a story
if b.Lanes[b.Current].Current < len(b.Lanes[b.Current].Stories)-1 {
b.Lanes[b.Current].Current += 1
} else {
b.SetMessage("Cannot move further down", true)
}
case 'k':
// move selection up a story
if b.Lanes[b.Current].Current > 0 {
b.Lanes[b.Current].Current -= 1
} else {
b.SetMessage("Cannot move further up", true)
}
case 'H':
// move story left a lane
if b.Current == 0 {
b.SetMessage("Cannot move story left", true)
break
}
storyIndex := b.Lanes[b.Current].Current
if len(b.Lanes[b.Current].Stories) <= 0 {
goto MoveLeft
}
b.Lanes[b.Current-1].Stories = append(b.Lanes[b.Current-1].Stories, b.Lanes[b.Current].Stories[storyIndex].Duplicate())
if storyIndex == len(b.Lanes[b.Current].Stories)-1 {
b.Lanes[b.Current].Stories = b.Lanes[b.Current].Stories[:len(b.Lanes[b.Current].Stories)-1]
b.Lanes[b.Current].Current -= 1
} else {
b.Lanes[b.Current].Stories[storyIndex] = b.Lanes[b.Current].Stories[len(b.Lanes[b.Current].Stories)-1]
b.Lanes[b.Current].Stories = b.Lanes[b.Current].Stories[:len(b.Lanes[b.Current].Stories)-1]
}
b.Lanes[b.Current-1].Current = len(b.Lanes[b.Current-1].Stories)-1
MoveLeft: b.Move('h')
case 'L':
// move story right a lane
if b.Current == len(b.Lanes)-1 {
b.SetMessage("Cannot move story right", true)
break
}
storyIndex := b.Lanes[b.Current].Current
if len(b.Lanes[b.Current].Stories) <= 0 {
goto MoveRight
}
b.Lanes[b.Current+1].Stories = append(b.Lanes[b.Current+1].Stories, b.Lanes[b.Current].Stories[storyIndex].Duplicate())
if storyIndex == len(b.Lanes[b.Current].Stories)-1 {
b.Lanes[b.Current].Stories = b.Lanes[b.Current].Stories[:len(b.Lanes[b.Current].Stories)-1]
b.Lanes[b.Current].Current -= 1
} else {
b.Lanes[b.Current].Stories[storyIndex] = b.Lanes[b.Current].Stories[len(b.Lanes[b.Current].Stories)-1]
b.Lanes[b.Current].Stories = b.Lanes[b.Current].Stories[:len(b.Lanes[b.Current].Stories)-1]
}
b.Lanes[b.Current+1].Current = len(b.Lanes[b.Current+1].Stories)-1
MoveRight: b.Move('l')
case 'K':
storyIndex := b.Lanes[b.Current].Current
if storyIndex <= 0 {
b.SetMessage("Cannot move story up", true)
break
}
swapper := reflect.Swapper(b.Lanes[b.Current].Stories)
swapper(storyIndex, storyIndex-1)
b.Move('k')
case 'J':
storyIndex := b.Lanes[b.Current].Current
if storyIndex > len(b.Lanes[b.Current].Stories)-2 {
b.SetMessage("Cannot move story down", true)
break
}
swapper := reflect.Swapper(b.Lanes[b.Current].Stories)
swapper(storyIndex, storyIndex+1)
b.Move('j')
}
}
func (b Board) Draw() {
var out strings.Builder
out.WriteString(cursorHome)

View File

@ -7,12 +7,13 @@ const (
)
type Styles struct {
Mode int
Header string
Message string
MessageErr string
Lane string
Input string
Mode int
Header string
Message string
MessageErr string
Lane string
LaneSelected string
Input string
}
@ -24,19 +25,24 @@ var colors = map[int]map[string]string{
"Message": "\033[97;42m", // bright white on green
"MessageErr": "\033[97;41m", // bright white on red
"Lane": "\033[30;104m", // black on bright blue
"Input": "\033[30;107m"}, // black on bright white
"LaneSelected": "\033[30;103m", // black on bright yellow
"Input": "\033[30;107m", // black on bright white
},
EightBitColor: map[string]string{
"Header": "\033[48;5;254m\033[38;5;21\033[1m",
"Message": "\033[48;5;35m\033[38;5;231m",
"MessageErr": "\033[48;5;124m\033[38;5;231m",
"Lane": "\033[48;5;63m\033[38;5;235m",
"Input": "\033[48;5;231m\033[38;5;235"},
"Input": "\033[48;5;231m\033[38;5;235",
},
TrueColor: map[string]string{
"Header": "",
"Message": "",
"MessageErr": "",
"Lane": "",
"Input": ""}}
"Input": "",
},
}
func (s *Styles) Init(mode int) {
if mode == TrueColor || mode == EightBitColor {
@ -44,9 +50,10 @@ func (s *Styles) Init(mode int) {
} else {
s.Mode = SimpleColor
}
s.Header = colors[s.Mode]["Header"]
s.Message = colors[s.Mode]["Message"]
s.MessageErr = colors[s.Mode]["MessageErr"]
s.Lane = colors[s.Mode]["Lane"]
s.Input = colors[s.Mode]["Input"]
s.Header = colors[s.Mode]["Header"]
s.Message = colors[s.Mode]["Message"]
s.MessageErr = colors[s.Mode]["MessageErr"]
s.Lane = colors[s.Mode]["Lane"]
s.LaneSelected = colors[s.Mode]["LaneSelected"]
s.Input = colors[s.Mode]["Input"]
}

28
lane.go
View File

@ -22,11 +22,33 @@ func (l *Lane) CreateStory(b *Board) {
}
}
func (l *Lane) StringSlice(width int) []string {
func (l Lane) Header(width int, selected bool) string {
marker := " "
color := style.Lane
if selected {
marker = "*"
color = style.LaneSelected
}
if len(l.Title) > width {
return fmt.Sprintf("%s\033[1;7m%*.*s%s%s", style.Lane, width, width, l.Title[:width-1], marker, styleOff)
} else {
width := width - 2
leftPad := (width - len(l.Title)) / 2
rightPad := width - len(l.Title) - leftPad
return fmt.Sprintf("%s %s\033[1;7m%*.*s%s%s%*.*s\033[27m%s %s", style.Lane, color, leftPad-1, leftPad-1, "", l.Title, marker, rightPad, rightPad, "", style.Lane, styleOff)
}
}
func (l Lane) StringSlice(width int, selected bool) []string {
out := make([]string, 0, len(l.Stories) * 3 + 1)
for _, story := range l.Stories {
for i, story := range l.Stories {
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", style.Lane, style.Input, width-2, width-2, 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, story.Title, style.Lane, styleOff))
}
if len(out) > 0 {
out = append(out, fmt.Sprintf("%s%*.*s%s", style.Lane, width, width, " ", styleOff))

View File

@ -59,11 +59,13 @@ func GetAndConfirmCommandLine(prefix string) (string, error) {
if conf == 'y' {
break
} else if conf == 'n' {
fmt.Print(cursorEnd)
continue
} else if conf == 'c' {
err = fmt.Errorf("Cancelled")
break
} else {
fmt.Print(cursorEnd)
goto VerifyQuery
}
}
@ -79,6 +81,7 @@ func main() {
Created: time.Now(),
Lanes: make([]Lane, 0, 1),
Current: -1,
LaneOff: 0,
Message: "Welcome to SWIM",
MsgErr: false,
Width: cols,

View File

@ -15,6 +15,22 @@ type Story struct {
Updated time.Time
}
func (s Story) Duplicate() Story {
out := Story{}
out.Title = s.Title
out.Body = s.Body
out.Users = make([]string, len(s.Users))
copy(out.Users, s.Users)
out.Tag = s.Tag
out.Tasks = make([]Task, len(s.Tasks))
copy(out.Tasks, s.Tasks)
out.Comments = make([]Comment, len(s.Comments))
copy(out.Comments, s.Comments)
out.Created = s.Created
out.Updated = s.Updated
return out
}
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()}
}

BIN
swim

Binary file not shown.